diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 9b77d545a3346e322adcdb2a4d0167b88dcc82d7..d432864b0c7a6e4ffc2d908aacd9c484f776be1a 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -1605,7 +1605,7 @@ function _M:detectTrap(trap, x, y, power) trap:setKnown(self, true, x, y) if self.player then game.level.map:updateMap(x, y) - game.logPlayer(self, "#AQUAMARINE#You notice a trap (%s)!", trap:getName()) + game.logPlayer(self, "#CADET_BLUE#You notice a trap (%s)!", trap:getName()) end end end diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index 83dfbc744e1bf24ef86fa3309daf5e95dada54a7..0ee416f31506c3299fa4d470433e8df5b64189db 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -328,12 +328,12 @@ function _M:use(who, typ, inven, item) end if self.use_sound then game:playSoundNear(who, self.use_sound) end if not ret.nobreakStepUp then who:breakStepUp() end - if not ret.nobreakStealth then who:breakStealth() end if not ret.nobreakLightningSpeed then who:breakLightningSpeed() end if not ret.nobreakReloading then who:breakReloading() end if not ret.nobreakSpacetimeTuning then who:breakSpacetimeTuning() end if not (self.use_no_energy or ret.no_energy) then who:useEnergy(game.energy_to_act * (inven.use_speed or 1)) + if not ret.nobreakStealth then who:breakStealth() end end end return ret diff --git a/game/modules/tome/class/interface/Archery.lua b/game/modules/tome/class/interface/Archery.lua index a158823972cd9430096ddd24e24558f145794c25..a5380fafd1317c209cd41a566f9720b6c2302129 100644 --- a/game/modules/tome/class/interface/Archery.lua +++ b/game/modules/tome/class/interface/Archery.lua @@ -385,38 +385,44 @@ local function archery_projectile(tx, ty, tg, self, tmp) if crit then self:logCombat(target, "#{bold}##Source# performs a ranged critical strike against #Target#!#{normal}#") end + if tg.archery.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 ammo and ammo.convert_damage then - for typ, conv in pairs(ammo.convert_damage) do - if dam > 0 then + -- Convert base damage to other damage types according to weapon and ammo + local ammo_conversion, weapon_conversion = 0, 0 + if dam > 0 then + local conv_dam + local archery_state = {is_archery=true} + if tmp then table.merge(archery_state, tmp) end + if ammo and ammo.convert_damage then -- convert to ammo damage types first + for typ, conv in pairs(ammo.convert_damage) do conv_dam = math.min(dam, dam * (conv / 100)) - conv_damtype = typ - dam = dam - conv_dam + print("[ATTACK ARCHERY]\tAmmo DamageType conversion%", conv, typ, conv_dam) + ammo_conversion = ammo_conversion + conv_dam if conv_dam > 0 then - DamageType:get(conv_damtype).projector(self, target.x, target.y, conv_damtype, math.max(0, conv_dam)) + DamageType:get(typ).projector(self, target.x, target.y, typ, conv_dam, archery_state) end end + dam = dam - ammo_conversion + print("[ATTACK ARCHERY]\t after Ammo DamageType conversion dam:", dam) end - end - - if weapon and weapon.convert_damage then - for typ, conv in pairs(weapon.convert_damage) do - if dam > 0 then + if weapon and weapon.convert_damage and dam > 0 then -- convert remaining damage to weapon damage types + for typ, conv in pairs(weapon.convert_damage) do conv_dam = math.min(dam, dam * (conv / 100)) - conv_damtype = typ - dam = dam - conv_dam + print("[ATTACK ARCHERY]\tWeapon DamageType conversion%", conv, typ, conv_dam) + weapon_conversion = weapon_conversion + conv_dam if conv_dam > 0 then - DamageType:get(conv_damtype).projector(self, target.x, target.y, conv_damtype, math.max(0, conv_dam)) + DamageType:get(typ).projector(self, target.x, target.y, typ, conv_dam, archery_state) end end + dam = dam - weapon_conversion + print("[ATTACK ARCHERY]\t after Weapon DamageType conversion dam:", dam) end - end - if tg.archery.crushing_blow then self:attr("crushing_blow", 1) end - DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam), tmp) + if dam > 0 then + DamageType:get(damtype).projector(self, target.x, target.y, damtype, dam, archery_state) + end + end if tg.archery.crushing_blow then self:attr("crushing_blow", -1) end if not tg.no_archery_particle then game.level.map:particleEmitter(target.x, target.y, 1, "archery") end @@ -424,7 +430,9 @@ local function archery_projectile(tx, ty, tg, self, tmp) if talent.archery_onhit then talent.archery_onhit(self, talent, target, target.x, target.y) end - target:fireTalentCheck("callbackOnArcheryHit", self) + -- add damage conversion back in so the total damage still gets passed + dam = dam + ammo_conversion + weapon_conversion + target:fireTalentCheck("callbackOnArcheryHit", self, dam) else self:logCombat(target, "#Source# misses #target#.") diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua index 76a1b5ec902a6d4c173905b2aee51c511eeb54cc..a8c91533634bd4fb003c480d9e9145120c880032 100644 --- a/game/modules/tome/class/interface/Combat.lua +++ b/game/modules/tome/class/interface/Combat.lua @@ -116,9 +116,8 @@ function _M:attackTarget(target, damtype, mult, noenergy, force_unarmed) 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:attr("stealth") and target:canSee(self) then + self:breakStealth() if self.player then self:logCombat(target, "#Target# notices you at the last moment!") end end @@ -523,6 +522,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam) local damrange = self:combatDamageRange(weapon) dam = rng.range(dam, dam * damrange) print("[ATTACK] after range", dam) + if deflect == 0 then dam, crit = self:physicalCrit(dam, weapon, target, atk, def) end print("[ATTACK] after crit", dam) dam = dam * mult @@ -566,27 +566,24 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam) 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 + -- Convert base damage to other types according to weapon local total_conversion = 0 - if weapon and weapon.convert_damage then + local melee_state = {is_melee=true} + if weapon and weapon.convert_damage and dam > 0 then + local conv_dam 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 + conv_dam = math.min(dam, dam * (conv / 100)) + print("[ATTACK]\tDamageType conversion%", conv, typ, conv_dam) + total_conversion = total_conversion + conv_dam + if conv_dam > 0 then + DamageType:get(typ).projector(self, target.x, target.y, typ, math.max(0, conv_dam), melee_state) end end + dam = dam - total_conversion + print("[ATTACK]\t after DamageType conversion dam:", dam) end - if dam > 0 then - DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam)) + DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam), melee_state) end if weapon and weapon.crushing_blow then self:attr("crushing_blow", -1) end @@ -599,9 +596,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam) end -- add damage conversion back in so the total damage still gets passed - if total_conversion > 0 then - dam = dam + total_conversion - end + dam = dam + total_conversion target:fireTalentCheck("callbackOnMeleeHit", self, dam) @@ -662,6 +657,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam) return self:combatSpeed(weapon), hitted, dam end +--- handle various on hit procs for melee combat 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, 2.5, 2) @@ -828,13 +824,48 @@ function _M:attackTargetHitProcs(target, weapon, dam, apr, armor, damtype, mult, end end - -- 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) - end - end end - + -- Reactive target on_melee_hit damage + if hitted then + local dr, fa, pct = 0 + for typ, dam in pairs(target.on_melee_hit) do + if not fa then + if self:knowTalent(self.T_CLOSE_COMBAT_MANAGEMENT) then + fa, pct = self:callTalent(self.T_CLOSE_COMBAT_MANAGEMENT, "reflectArmour", weapon) + print("[ATTACK]\tresolving on_melee_hit damage with:", fa, "flat_armor", pct, "% reflect") + else fa, pct = 0, 0 + end + end + local DT = DamageType:get(typ) + if type(dam) == "number" then + if dam > 0 then + dr = math.min(dam, fa) + print("[ATTACK]\ttarget on_melee_hit:", dam, typ, "vs", fa) + dam = dam - dr + if dam > 0 then DT.projector(target, self.x, self.y, typ, dam) end + if dr > 0 and pct > 0 then + dr = math.floor(dr*pct/100) + if dr > 0 then + print("[ATTACK]\ttarget on_melee_hit", dr, typ, "reflected") + DT.projector(self, target.x, target.y, typ, dr) + end + end + end + elseif dam.dam and dam.dam > 0 then + dr = math.min(dam.dam, fa) + print("[ATTACK]\ttarget on_melee_hit:", dam.dam, typ, "vs", fa) + if dr > 0 then dam = table.clone(dam); dam.dam = dam.dam - dr end + if dam.dam > 0 then DT.projector(target, self.x, self.y, typ, dam) end + if dr > 0 and pct > 0 then + dr = math.floor(dr*pct/100) + if dr > 0 then + print("[ATTACK]\ttarget on_melee_hit", dr, typ, "reflected") + dam.dam = dr -- only change dam + DT.projector(self, target.x, target.y, typ, dam) + end + end + end + end + end -- Acid splash if hitted and not target.dead and target:knowTalent(target.T_ACID_BLOOD) then local t = target:getTalentFromId(target.T_ACID_BLOOD) @@ -1079,7 +1110,8 @@ function _M:attackTargetHitProcs(target, weapon, dam, apr, armor, damtype, mult, self.logCombat(target, self, "#Source# counter attacks #Target# with %s shield shards!", string.his_her(target)) target:attackTarget(self, DamageType.NATURE, self:combatTalentWeaponDamage(t, 0.4, 1), true) end - + -- pass all parameters here? +--function _M:attackTargetHitProcs(target, weapon, dam, apr, armor, damtype, mult, atk, def, hitted, crit, evaded, repelled, old_target_life) self:fireTalentCheck("callbackOnMeleeAttack", target, hitted, crit, weapon, damtype, mult, dam) local hd = {"Combat:attackTargetWith", hitted=hitted, crit=crit, target=target, weapon=weapon, damtype=damtype, mult=mult, dam=dam} @@ -1171,11 +1203,12 @@ end --- Fake denotes a check not actually being made, used by character sheets etc. function _M:combatDefenseBase(fake) local add = 0 + local light_armor = self:hasLightArmor() if not self:attr("encased_in_ice") then if self:hasDualWeapon() and self:knowTalent(self.T_DUAL_WEAPON_DEFENSE) then add = add + self:callTalent(self.T_DUAL_WEAPON_DEFENSE,"getDefense") end - if self:hasLightArmor() and self:knowTalent(self.T_LIGHT_ARMOUR_TRAINING) then + if light_armor and self:knowTalent(self.T_LIGHT_ARMOUR_TRAINING) then add = add + self:callTalent(self.T_LIGHT_ARMOUR_TRAINING,"getDefense") end if not fake then @@ -1199,16 +1232,14 @@ function _M:combatDefenseBase(fake) end local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + (self:getLck() - 50) * 0.4) local mult = 1 - - if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then - mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef") - end - - if self:hasLightArmor() and self:hasEffect(self.EFF_MOBILE_DEFENCE) then + if light_armor then + if self:knowTalent(self.T_MOBILE_DEFENCE) then + mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef") + end local eff = self:hasEffect(self.EFF_MOBILE_DEFENCE) - mult = mult + (eff.power/100) + if eff then mult = mult + (eff.power/100) end end - + return math.max(0, d * mult + add) -- Add bonuses last to avoid compounding defense multipliers from talents end @@ -1268,11 +1299,14 @@ function _M:combatArmorHardiness() add = add + ga.getArmorHardiness(self, ga) end end - if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then - add = add + self:callTalent(self.T_MOBILE_DEFENCE, "getHardiness") - end - if self:hasLightArmor() and self:knowTalent(self.T_LIGHT_ARMOUR_TRAINING) then - add = add + self:callTalent(self.T_LIGHT_ARMOUR_TRAINING, "getArmorHardiness") + local light_armor = self:hasLightArmor() + if light_armor then + if self:knowTalent(self.T_MOBILE_DEFENCE) then + add = add + self:callTalent(self.T_MOBILE_DEFENCE, "getHardiness") + end + if self:knowTalent(self.T_LIGHT_ARMOUR_TRAINING) then + add = add + self:callTalent(self.T_LIGHT_ARMOUR_TRAINING, "getArmorHardiness") + end end if self:knowTalent(self.T_ARMOUR_OF_SHADOWS) and not game.level.map.lites(self.x, self.y) then add = add + 50 @@ -1765,6 +1799,9 @@ function _M:combatFatigue() if self:knowTalent(self["T_RESHAPE_WEAPON/ARMOUR"]) then fatigue = fatigue - self:callTalent(self["T_RESHAPE_WEAPON/ARMOUR"], "getFatigueBoost") end + if self:knowTalent(self.T_LIGHT_ARMOUR_TRAINING) then + fatigue = fatigue - self:callTalent(self.T_LIGHT_ARMOUR_TRAINING, "getFatigue") + end if fatigue < min then return min end if self:knowTalent(self.T_NO_FATIGUE) then return min end return fatigue @@ -1861,7 +1898,7 @@ function _M:physicalCrit(dam, weapon, target, atk, def, add_chance, crit_power_a chance = chance - target:callTalent(target.T_SCOUNDREL,"getCritPenalty") end - if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and target and not target:canSee(self) then -- bug fix + if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and target and not target:canSee(self) then chance = 100 end diff --git a/game/modules/tome/class/interface/TooltipsData.lua b/game/modules/tome/class/interface/TooltipsData.lua index 44b4dd471fe1d7115fe5f0af52c57ddfd3bca47d..3226ef68df4f7ad578588c395f746679e6d69d17 100644 --- a/game/modules/tome/class/interface/TooltipsData.lua +++ b/game/modules/tome/class/interface/TooltipsData.lua @@ -425,7 +425,7 @@ TOOLTIP_SPECIFIC_IMMUNE = [[#GOLD#Effect resistance chance#LAST# This represents your chance to completely avoid this specific effect. ]] TOOLTIP_STUN_IMMUNE = [[#GOLD#Stun immunity chance#LAST# -This represents your chance to completely being stunned, dazed, or frozen. +This represents your chance to completely avoid being stunned, dazed, or frozen. ]] TOOLTIP_INSTAKILL_IMMUNE = [[#GOLD#Instant death resistance#LAST# This represents your chance to avoid being instantly killed, severely incapacitated, or controlled by certain abilities. diff --git a/game/modules/tome/data/general/traps/complex.lua b/game/modules/tome/data/general/traps/complex.lua index 816cb4a5ab2e19ec73e9aa70710f10186869fd6d..6ec2276fae6ef3684a2ef446b6016e4c2cf5d869 100644 --- a/game/modules/tome/data/general/traps/complex.lua +++ b/game/modules/tome/data/general/traps/complex.lua @@ -40,7 +40,7 @@ newEntity{ base = "TRAP_COMPLEX", desc = function(self) local dir = game.level.map:compassDirection(self.spawn_x - self.x, self.spawn_y - self.y) dir = dir and (" (from %s)"):format(dir) or "" - return ("Releases a large boulder%s that smashes into the target for %d physical damage and knocking it back."):format(dir,self.dam) + return ("Releases a large boulder%s that smashes into the target for %d physical damage and knocks it back."):format(dir,self.dam) end, pressure_trap = true, on_added = function(self, level, x, y) diff --git a/game/modules/tome/data/talents/cunning/artifice.lua b/game/modules/tome/data/talents/cunning/artifice.lua index 92628c53f702cff7a9b090e4d55fee21230c83fe..b8cde31915717746262c9a1828094ec52ce7497d 100644 --- a/game/modules/tome/data/talents/cunning/artifice.lua +++ b/game/modules/tome/data/talents/cunning/artifice.lua @@ -551,18 +551,17 @@ newTalent{ return true end, short_info = function(self, t, slot_talent) - return ([[Throw a smokebomb creating a radius 2 cloud of smoke, lasting %d turns, that blocks sight and reduces enemies' vision by %d. 15 turn cooldown.]]):format(t.getSightLoss(self, slot_talent), t.getDuration(self, slot_talent)) + return ([[Throw a smokebomb creating a radius 2 cloud of smoke, lasting %d turns, that blocks sight and reduces enemies' vision by %d. 15 turn cooldown.]]):format(t.getDuration(self, slot_talent), t.getSightLoss(self, slot_talent)) end, info = function(self, t) local slot = "not prepared" for slot_id, tool_id in pairs(self.artifice_tools) do if tool_id == t.id then slot = self:getTalentFromId(slot_id).name break end end - return ([[Throw a vial of volatile liquid that explodes in a smoke cloud of radius %d, blocking line of sight for 5 turns. Enemies within will have their vision range reduced by %d. - Creatures affected by smokescreen can never prevent you from activating stealth, even if their proximity would normally forbid it. - Use of this talent will not break stealth. + return ([[Throw a vial of volatile liquid that explodes in a radius %d cloud of smoke lasting %d turns. The smoke blocks line of sight, and enemies within will have their vision range reduced by %d. + Use of this talent will not break stealth, and creatures affected by the smokes can never prevent you from activating stealth, even if their proximity would normally forbid it. #YELLOW#Prepared with: %s#LAST#]]): - format(self:getTalentRadius(t), t.getSightLoss(self,t), slot) + format(self:getTalentRadius(t), t.getDuration(self, t), t.getSightLoss(self,t), slot) end, } diff --git a/game/modules/tome/data/talents/cunning/lethality.lua b/game/modules/tome/data/talents/cunning/lethality.lua index 436c5dee0c727b4dfb231689e4c235320c052428..a6de45cc0864aacd4fa619de1cdcd057d103bf3f 100644 --- a/game/modules/tome/data/talents/cunning/lethality.lua +++ b/game/modules/tome/data/talents/cunning/lethality.lua @@ -32,7 +32,7 @@ newTalent{ info = function(self, t) local critchance = t.getCriticalChance(self, t) local power = t.critpower(self, t) - return ([[You have learned to find and hit weak spots. All your strikes have a %0.2f%% greater chance to be critical hits, and your critical hits do %0.1f%% more damage. + return ([[You have learned to find and hit weak spots. All your strikes have a %0.1f%% greater chance to be critical hits, and your critical hits do %0.1f%% more damage. Also, when using knives, you now use your Cunning instead of your Strength for bonus damage.]]): format(critchance, power) end, @@ -138,7 +138,7 @@ newTalent{ tactical = { BUFF = 1 }, fixed_cooldown = true, getTalentCount = function(self, t) return math.floor(self:combatTalentScale(t, 2, 7, "log")) end, - getMaxLevel = function(self, t) return self:getTalentLevel(t) end, + getMaxLevel = function(self, t) return math.max(1, self:getTalentLevelRaw(t)) end, speed = "combat", action = function(self, t) local tids = {} diff --git a/game/modules/tome/data/talents/cunning/poisons.lua b/game/modules/tome/data/talents/cunning/poisons.lua index aa621a20b2e10c67f6bc03d47093bc6e06c1daf2..8582600ca74e6a024cca5751d352aa477e18c9f7 100644 --- a/game/modules/tome/data/talents/cunning/poisons.lua +++ b/game/modules/tome/data/talents/cunning/poisons.lua @@ -105,27 +105,37 @@ newTalent{ points = 5, mode = "passive", require = cuns_req2, - getRadius = function(self, t) return self:combatTalentScale(t, 1, 3, "log") end, + getRadius = function(self, t) return math.floor(self:combatTalentLimit(t, 10, 1, 2.7)) end, + getChance = function(self, t) return self:combatTalentLimit(t, 50, 10, 25) end, on_kill = function(self, t, target) local poisons = {} + local to_spread = 0 for k, v in pairs(target.tmp) do local e = target.tempeffect_def[k] - if e.subtype.poison and v.src and v.src == self then - poisons[k] = target:copyEffect(k) + if e.subtype.poison and v.src == self and rng.percent(t.getChance(self, t)) then + print("[Toxic Death] spreading poison", k, target.x, target.y) + poisons[k] = target:copyEffect(k) poisons[k]._from_toxic_death = true + to_spread = to_spread + 1 end end - - local tg = {type="ball", range = 10, radius=t.getRadius(self, t), selffire = false, friendlyfire = false, talent=t} - self:project(tg, target.x, target.y, function(tx, ty) - local target2 = game.level.map(tx, ty, Map.ACTOR) - if not target2 or target2 == self then return end - for eff, p in pairs(poisons) do - target2:setEffect(eff, p.dur, table.clone(p)) - end - end) + if to_spread > 0 then + local tg = {type="ball", range = 10, radius=t.getRadius(self, t), selffire = false, friendlyfire = false, talent=t, stop_block=true} + target.dead = false -- for combat log purposes + game.logSeen(target, "#GREEN#Poison bursts out of %s's corpse!", target.name:capitalize()) + game.level.map:particleEmitter(target.x, target.y, tg.radius, "slime") + self:project(tg, target.x, target.y, function(tx, ty) + local target2 = game.level.map(tx, ty, Map.ACTOR) + if not target2 or target2 == target then return end + for eff, p in pairs(poisons) do + game:playSoundNear(target, "creatures/jelly/jelly_hit") + if p.unresistable or target2:canBe("poison") then target2:setEffect(eff, p.dur, table.clone(p)) end + end + end) + target.dead = true + end end, info = function(self, t) - return ([[When you kill a creature, all the poisons affecting it will have a %d%% chance to spread to foes in a radius of %d.]]):format(20 + self:getTalentLevelRaw(t) * 8, t.getRadius(self, t)) + return ([[When you kill a creature, all of your poisons affecting it will have a %d%% chance to spread to foes in a radius of %d.]]):format(t.getChance(self, t), t.getRadius(self, t)) end, } @@ -381,7 +391,7 @@ newTalent{ no_energy = true, tactical = { BUFF = 2 }, no_unlearn_last = true, - getEffect = function(self, t) return self:combatTalentLimit(self:getTalentLevel(self.T_VILE_POISONS), 15, 2, 8) end, + getEffect = function(self, t) return self:combatTalentLimit(self:getTalentLevel(self.T_VILE_POISONS), 100, 10, 40) end, -- limit < 50% activate = function(self, t) cancelPoisons(self) self.vile_poisons = self.vile_poisons or {} @@ -393,7 +403,7 @@ newTalent{ return true end, info = function(self, t) - return ([[Enhances your Deadly Poison with a leeching agent, causing all damage you deal to targets affected by your deadly poison to heal you for %d%%.]]): + return ([[Enhances your Deadly Poison with a leeching agent, causing it to heal you for %d%% of the damage it does to its target.]]): format(t.getEffect(self, t)) end, } @@ -432,7 +442,7 @@ newTalent{ points = 1, mode = "passive", no_unlearn_last = true, - getDamage = function(self, t) return 10 + self:getMag() end, + getDamage = function(self, t) return self:combatStatScale("mag", 20, 50, 0.75) end, info = function(self, t) return ([[Whenever you apply Deadly Poison, you also apply an unresistable magical poison dealing %0.2f arcane damage (based on your Magic) each turn. This poison reduces all damage resistance by 10%% and poison immunity (for living targets) by 50%%.]]): format(damDesc(self, DamageType.ARCANE, t.getDamage(self,t))) @@ -455,7 +465,7 @@ newTalent{ vile_poison = true, getDuration = function(self, t) return math.floor(self:combatTalentScale(self:getTalentLevel(self.T_VILE_POISONS), 6, 8)) end, getDOT = function(self, t) return 8 + self:combatTalentStatDamage(self.T_VILE_POISONS, "cun", 10, 30) * 0.4 end, - stoneTime = function(self, t) return math.ceil(self:combatTalentLimit(self:getTalentLevel(self.T_VILE_POISONS), 1, 11, 7)) end, -- Time to stone target, always > 1 turn + stoneTime = function(self, t) return math.ceil(self:combatTalentLimit(self:getTalentLevel(self.T_VILE_POISONS), 1, 10, 5.6)) end, -- Time to stone target, always > 1 turn getEffect = function(self, t) return math.floor(self:combatTalentScale(self:getTalentLevel(self.T_VILE_POISONS), 3, 4)) end, on_learn = function(self, t) self.__show_special_talents[t.id] = true diff --git a/game/modules/tome/data/talents/cunning/scoundrel.lua b/game/modules/tome/data/talents/cunning/scoundrel.lua index 0c8e68f645a02b2bc9950d0d96bf94d3fc742477..20a3602ee61e5c124d3bc012c6be7792ac62d526 100644 --- a/game/modules/tome/data/talents/cunning/scoundrel.lua +++ b/game/modules/tome/data/talents/cunning/scoundrel.lua @@ -28,14 +28,14 @@ newTalent{ no_break_stealth = true, getChance = function(self,t) return self:combatTalentLimit(t, 50, 15, 35) end, --Limit < 50% callbackOnMeleeAttack = function(self, t, target, hitted, crit, weapon, damtype, mult, dam) - if not target then return nil end - if self:reactionToward(target) >=0 then return nil end + if not (target and hitted and dam > 0) or self:reactionToward(target) >=0 then return nil end if rng.percent(t.getChance(self, t)) then if target:canBe("cut") then - local energyDrain = (game.energy_to_act * 0.1) - target.energy.value = target.energy.value - energyDrain - local bleed = dam*0.75 - target:setEffect(target.EFF_CUT, 10, {src=self, power=(dam / 10)}) + target:setEffect(target.EFF_CUT, 10, {src=self, power=(dam*.75 / 10)}) + if not target.turn_procs.lacerating_strikes then + target:useEnergy(game.energy_to_act * 0.1) + target.turn_procs.lacerating_strikes = true + end end end end, @@ -47,7 +47,7 @@ newTalent{ end, info = function(self, t) local chance = t.getChance(self,t) - return ([[Your melee attacks have a %d%% chance to inflict a deep, disabling wound inflicting an additional 75%% of the damage dealt as a bleed over 10 turns, as well as causing the target to lose 10%% of a turn.]]): + return ([[Your melee attacks have a %d%% chance to inflict a deep, disabling wound inflicting an additional 75%% of the damage dealt as a bleed over 10 turns, as well as causing the target (up to once per turn) to lose 10%% of a turn.]]): format(chance) end, } @@ -59,45 +59,45 @@ newTalent{ mode = "passive", points = 5, getCritPenalty = function(self,t) return self:combatTalentScale(t, 10, 30) end, - getDuration = function(self,t) return math.floor(self:combatTalentScale(t, 2, 4)) end, + getDuration = function(self,t) return math.floor(self:combatTalentScale(t, 2, 4, "log")) end, getChance = function(self, t) return self:combatTalentLimit(t, 100, 8, 24) end, -- Limit < 100% callbackOnMeleeAttack = function(self, t, target, hitted, crit, weapon, damtype, mult, dam) - if not target then return nil end - if self:reactionToward(target) >=0 then return nil end - if target then target:setEffect(target.EFF_SCOUNDREL, 10, {src=self, power=t.getCritPenalty(self,t) }) end - if self:knowTalent(self.T_FUMBLE) and target then + if not (target and hitted and dam > 0) or self:reactionToward(target) >=0 then return nil end + target:setEffect(target.EFF_SCOUNDREL, 10, {src=self, power=t.getCritPenalty(self,t) }) + if self:knowTalent(self.T_FUMBLE) then local dam = self:callTalent(self.T_FUMBLE, "getDamage") local stacks = self:callTalent(self.T_FUMBLE, "getStacks") target:setEffect(target.EFF_FUMBLE, 10, {power=3, max_power = stacks*3, dam=dam, stacks=1, max_stacks=stacks }) end - local nb = 0 + if target.turn_procs.scoundrel or not rng.percent(t.getChance(self,t)) then return end + local bleed = false for eff_id, p in pairs(target.tmp) do local e = target.tempeffect_def[eff_id] - if e.subtype.cut then nb = nb + 1 end + if e.subtype.cut then bleed = true break end end - if nb <= 0 or not rng.percent(t.getChance(self,t)) or target.turn_procs.scoundrel then return end - local tids = {} - for tid, lev in pairs(target.talents) do - local t = target:getTalentFromId(tid) - if t and not target.talents_cd[tid] and t.mode == "activated" and not t.innate then tids[#tids+1] = t end + if bleed then + local tids = {} + for tid, lev in pairs(target.talents) do + local t = target:getTalentFromId(tid) + if t and not target.talents_cd[tid] and t.mode == "activated" and not t.innate then tids[#tids+1] = t end + end + + local cd = t.getDuration(self,t) + local t = rng.tableRemove(tids) + if not t or #tids<=0 then return end + target.talents_cd[t.id] = cd + game.logSeen(target, "#CRIMSON#%s's %s is disrupted by %s wounds!", target.name:capitalize(), t.name, target:his_her()) + target.turn_procs.scoundrel = true end - - local cd = t.getDuration(self,t) - local t = rng.tableRemove(tids) - if not t or #tids<=0 then return end - target.talents_cd[t.id] = cd - game.logSeen(target, "%s's %s is disrupted by their wounds!", target.name:capitalize(), t.name) - target.turn_procs.scoundrel = true - end, info = function(self, t) local chance = t.getChance(self,t) local crit = t.getCritPenalty(self, t) local dur = t.getDuration(self,t) return ([[Your melee attacks inflict distracting wounds that reduce the target’s critical strike chance by %d%% for 10 turns. -In addition, your attacks against bleeding targets have a %d%% chance to inflict a painful wound that causes them to forget a random talent for %d turns. This cannot affect a target more than once per turn. +In addition, your attacks against bleeding targets have a %d%% chance to inflict a painful wound that causes them to forget a random talent for %d turns. The last effect cannot occur more than once per turn per target. ]]):format(crit, chance, dur) end, } @@ -151,7 +151,7 @@ newTalent{ local stacks = t.getStacks(self, t) local dam = t.getDamage(self, t) return ([[Your Scoundrel's Strategies effect leaves your foes unable to focus on any complex actions, giving them a stacking 3%% chance of failure the next time they try to use a talent (to a maximum of %d%%). - If the talent fails, the target fumbles and injures themselves, taking %0.2f physical damage and removing the fumble effect. + If the talent fails, the target fumbles and injures itself, taking %0.2f physical damage and removing the fumble effect. The damage dealt increases with your Cunning. ]]):format(stacks*3, damDesc(self, DamageType.PHYSICAL, dam)) end, diff --git a/game/modules/tome/data/talents/cunning/stealth.lua b/game/modules/tome/data/talents/cunning/stealth.lua index c475e57bb00cf80f62b6d5ce0fcbb2ba68922839..79b5ab98183784775454712ac49d0fce3f5832e1 100644 --- a/game/modules/tome/data/talents/cunning/stealth.lua +++ b/game/modules/tome/data/talents/cunning/stealth.lua @@ -37,6 +37,16 @@ local function stealthDetection(self, radius, estimate) end Talents.stealthDetection = stealthDetection +-- radius of detection for stealth talents +local function stealthRadius(self, t, fake) + local base = math.ceil(self:combatTalentLimit(t, 0, 8.9, 4.6)) -- Limit to range >= 1 + local sooth = self:callTalent(self.T_SOOTHING_DARKNESS, "getRadius", fake) + local final = math.max(0, base - sooth) + if fake then return base, final + else return final + end +end + newTalent{ name = "Stealth", type = {"cunning/stealth", 1}, @@ -49,7 +59,7 @@ newTalent{ tactical = { BUFF = 3 }, no_break_stealth = true, getStealthPower = function(self, t) return math.max(0, self:combatScale(self:getCun(10, true) * self:getTalentLevel(t), 15, 1, 64, 50)) end, --TL 5, cun 100 = 64 - getRadius = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 8.9, 4.6)) end, -- Limit to range >= 1 + getRadius = stealthRadius, on_pre_use = function(self, t, silent, fake) local armor = self:getInven("BODY") and self:getInven("BODY")[1] if armor and (armor.subtype == "heavy" or armor.subtype == "massive") then @@ -60,7 +70,6 @@ newTalent{ -- Check nearby actors detection ability if not self.x or not self.y or not game.level then return end - if self:knowTalent(self.T_SOOTHING_DARKNESS) and not game.level.map.lites(self.x, self.y) then return true end if not rng.percent(self.hide_chance or 0) then if stealthDetection(self, t.getRadius(self, t)) > 0 then if not silent then game.logPlayer(self, "You are being observed too closely to enter Stealth!") end @@ -74,10 +83,8 @@ newTalent{ if self:knowTalent(self.T_SOOTHING_DARKNESS) then local life = self:callTalent(self.T_SOOTHING_DARKNESS, "getLife") local sta = self:callTalent(self.T_SOOTHING_DARKNESS, "getStamina") - local dr = self:callTalent(self.T_SOOTHING_DARKNESS, "getReduction") local dur = self:callTalent(self.T_SOOTHING_DARKNESS, "getDuration") - - self:setEffect(self.EFF_SOOTHING_DARKNESS, dur, {life=life, stamina=sta, dr=dr}) + self:setEffect(self.EFF_SOOTHING_DARKNESS, dur, {life=life, stamina=sta}) end if self:knowTalent(self.T_SHADOWSTRIKE) then @@ -104,8 +111,11 @@ newTalent{ local t = self:getTalentFromId(self.T_TERRORIZE) t.terrorize(self,t) end - if self:hasEffect(self.EFF_SHADOW_DANCE) then - self:removeEffect(self.EFF_SHADOW_DANCE) + + local sd = self:hasEffect(self.EFF_SHADOW_DANCE) + if sd then + sd.no_cancel_stealth = true + self:removeEffect(sd.effect_id) end self:resetCanSeeCacheOf() if self.updateMainShader then self:updateMainShader() end @@ -115,10 +125,8 @@ newTalent{ if self:knowTalent(self.T_SOOTHING_DARKNESS) then local life = self:callTalent(self.T_SOOTHING_DARKNESS, "getLife") local sta = self:callTalent(self.T_SOOTHING_DARKNESS, "getStamina") - local dr = self:callTalent(self.T_SOOTHING_DARKNESS, "getReduction") local dur = self:callTalent(self.T_SOOTHING_DARKNESS, "getDuration") - - self:setEffect(self.EFF_SOOTHING_DARKNESS, dur, {life=life, stamina=sta, dr=dr}) + self:setEffect(self.EFF_SOOTHING_DARKNESS, dur, {life=life, stamina=sta}) end if self:knowTalent(self.T_SHADOWSTRIKE) then @@ -131,13 +139,14 @@ newTalent{ end, info = function(self, t) local stealthpower = t.getStealthPower(self, t) + (self:attr("inc_stealth") or 0) - local radius = t.getRadius(self, t) + local radius, rad_dark = t.getRadius(self, t, true) + xs = rad_dark ~= radius and (" (range %d in an unlit grid)"):format(rad_dark) or "" return ([[Enters stealth mode (power %d, based on Cunning), making you harder to detect. If successful (re-checked each turn), enemies will not know exactly where you are, or may not notice you at all. Stealth reduces your light radius to 0, and will not work with heavy or massive armours. - You cannot enter stealth if there are foes in sight within range %d. - Any non-instant, non-movement actions will break stealth if not otherwise specified.]]): - format(stealthpower, radius) + You cannot enter stealth if there are foes in sight within range %d%s. + Any non-instant, non-movement action will break stealth if not otherwise specified.]]): + format(stealthpower, radius, xs) end, } @@ -163,15 +172,18 @@ newTalent{ require = cuns_req3, points = 5, mode = "passive", - getLife = function(self, t) return self:combatTalentScale(t, 2, 10) end, --12 @TL5, mostly useful for out of combat as this won't do much in a fight + getLife = function(self, t) return self:combatStatScale("cun", 0.5, 5, 0.75) + self:combatTalentScale(t, 0.5, 5, 0.75) end, -- Primarily for out of combat recovery getStamina = function(self, t) return self:combatTalentScale(t, 1, 2.5) end, --2.9 @TL5 - getReduction = function(self, t) return self:combatTalentStatDamage(t, "cun", 10, 35) end, --29 @100 Cun, effectively about 40% weaker than Automated Cloak Tesselation with less uptime + getRadius = function(self, t, fake) + if not fake and game.level.map.lites(self.x, self.y) then return 0 end + return math.floor(self:combatTalentLimit(t, 10, 2, 5)) + end, getDuration = function(self,t) if self:getTalentLevel(t) >= 3 then return 3 else return 2 end end, info = function(self, t) - return ([[You have a special affinity for darkness. - When standing in an unlit grid, enemies observing you will not prevent you from entering stealth. - While stealthed, all damage against you is reduced by %d, your life regeneration is increased by %0.1f, and your stamina regeneration is increased %0.1f. This effect persists for 2 turns after exiting stealth, or 3 turns at talent level 3 and above.]]): - format(t.getReduction(self,t), t.getLife(self,t), t.getStamina(self,t)) + return ([[You have a special affinity for darkness and shadows. + When standing in an unlit grid, the minimum range to your foes for activating stealth or for maintaining it after a Shadow Dance is reduced by %d. + While stealthed, and for %d turns thereafter, your life regeneration is increased by %0.1f (based on your Cunning) and your stamina regeneration is increased %0.1f.]]): + format(t.getRadius(self, t, true), t.getDuration(self, t), t.getLife(self,t), t.getStamina(self,t)) end, } @@ -185,32 +197,26 @@ newTalent{ stamina = 30, cooldown = function(self, t) return self:combatTalentLimit(t, 10, 30, 15) end, tactical = { DEFEND = 2, ESCAPE = 2 }, - on_pre_use = function(self, t, silent) - if self:isTalentActive(self.T_STEALTH) then - if not silent then game.logPlayer(self, "You must be out of stealth to enter Shadow Dance.") end - return false - end - return true - end, - getRadius = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 8.9, 4.6)) end, -- Limit to range >= 1 + getRadius = stealthRadius, getDuration = function(self, t) return 1 + math.min(self:combatTalentScale(t, 1, 3),3) end, action = function(self, t) - if self:isTalentActive(self.T_STEALTH) then return end - - self:forceUseTalent(self.T_STEALTH, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true}) - for act, param in pairs(self.fov.actors) do - if act ~= self and act.ai_target and act.ai_target.actor == self then act:setTarget() end + if not self:isTalentActive(self.T_STEALTH) then + self:forceUseTalent(self.T_STEALTH, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true}) + for act, param in pairs(self.fov.actors) do + if act ~= self and act.ai_target and act.ai_target.actor == self then act:setTarget() end + end end - + self:alterTalentCoolingdown(self.T_STEALTH, -20) self:setEffect(self.EFF_SHADOW_DANCE, t.getDuration(self,t), {src=self, rad=t.getRadius(self,t)}) return true end, info = function(self, t) - return ([[Your mastery of stealth allows you to vanish from sight, returning to stealth and causing stealth to no longer break from unstealthy actions for %d turns. - When triggered, all enemies with a direct line of sight to you completely lose track of you. - When this effect ends, you must make a stealth check against targets in radius %d or be revealed. -You must be unstealthed to use this talent.]]): - format(t.getDuration(self, t), t.getRadius(self,t)) + local radius, rad_dark = t.getRadius(self, t, true) + xs = rad_dark ~= radius and (" (range %d in an unlit grid)"):format(rad_dark) or "" + return ([[Your mastery of stealth allows you to vanish from sight at any time. + You automatically enter stealth mode, reset its cooldown, and cause it to not break from unstealthy actions for %d turns. If you were not already stealthed, all enemies in a direct line of sight completely lose track of you. + When your Shadow Dance ends, you must make a stealth check against targets in radius %d%s or be revealed.]]): + format(t.getDuration(self, t), radius, xs) end, } \ No newline at end of file diff --git a/game/modules/tome/data/talents/cunning/survival.lua b/game/modules/tome/data/talents/cunning/survival.lua index 84768734dd5171380e645bb3f8442f06f9d45c6c..1e1a63be96d80282deca90e2cf00f1bc3d68ffdb 100644 --- a/game/modules/tome/data/talents/cunning/survival.lua +++ b/game/modules/tome/data/talents/cunning/survival.lua @@ -17,6 +17,14 @@ -- Nicolas Casalini "DarkGod" -- darkgod@te4.org +--[[ ideas: + Heightened Senses: restore trap detection, remove unseen attackers damage reduction + Device Mastery: remove trap detection, leave trap disarming + Danger Sense: get damage from unseen attackers, reduce general damage reduction, + gain? trap detection? blind-fighting like ability? + +--]] + newTalent{ name = "Heightened Senses", type = {"cunning/survival", 1}, @@ -24,7 +32,7 @@ newTalent{ mode = "passive", points = 5, sense = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end, - seePower = function(self, t) return self:combatScale(self:getCun(15, true)*self:getTalentLevel(t), 5, 0, 80, 75) end, --I5 + seePower = function(self, t) return self:combatScale(self:getCun(15, true)*self:getTalentLevel(t), 5, 0, 80, 75) end, getResists = function(self, t) return self:combatTalentLimit(t, 40, 5, 25) end, passives = function(self, t, p) self:talentTemporaryValue(p, "heightened_senses", t.sense(self, t)) @@ -44,6 +52,29 @@ newTalent{ end, } +newTalent{ + name = "Device Mastery", + type = {"cunning/survival", 2}, + require = cuns_req2, + mode = "passive", + points = 5, + cdReduc = function(tl) + if tl <=0 then return 0 end + return math.floor(100*tl/(tl+7.5)) -- Limit < 100% + end, + passives = function(self, t, p) + self:talentTemporaryValue(p, "use_object_cooldown_reduce", t.cdReduc(self:getTalentLevel(t))) + end, + autolearn_talent = "T_DISARM_TRAP", + trapPower = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getCun(25, true), 0, 0, 125, 125) end, + info = function(self, t) + return ([[Your cunning manipulations allow you to use charms (wands, totems and torques) more efficiently, reducing their cooldowns and the power cost of all usable items by %d%%. +In addition, your knowledge of devices allows you to detect traps around you and disarm known traps (%d detection and disarm 'power'). +The trap detection and disarming ability improves with your Cunning. ]]): + format(t.cdReduc(self:getTalentLevel(t)), t.trapPower(self,t)) --I5 + end, +} + newTalent{ name = "Track", type = {"cunning/survival", 2}, @@ -68,67 +99,51 @@ newTalent{ end, } -newTalent{ - name = "Device Mastery", - type = {"cunning/survival", 3}, - require = cuns_req3, - mode = "passive", - points = 5, - cdReduc = function(tl) - if tl <=0 then return 0 end - return math.floor(100*tl/(tl+7.5)) -- Limit < 100% - end, - passives = function(self, t, p) - self:talentTemporaryValue(p, "use_object_cooldown_reduce", t.cdReduc(self:getTalentLevel(t))) - end, - on_learn = function(self, t) - if not self:knowTalent(self.T_DISARM_TRAP) then - self:learnTalent(self.T_DISARM_TRAP, true, 1) - end - end, - on_unlearn = function(self, t) - if self:getTalentLevel(t) < 1 and self:knowTalent(self.T_DISARM_TRAP) then - self:unlearnTalent(self.T_DISARM_TRAP, 1) - end - end, - trapPower = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getCun(25, true), 0, 0, 125, 125) end, - info = function(self, t) - return ([[Your cunning manipulations allow you to use charms (wands, totems and torques) more efficiently, reducing their cooldowns and the power cost of all usable items by %d%%. -In addition, your knowledge of devices allows you to detect traps around you and disarm known traps (%d detection and disarm 'power'). -The trap detection and disarming ability improves with your Cunning. ]]): - format(t.cdReduc(self:getTalentLevel(t)), t.trapPower(self,t)) --I5 - end, -} - +--- This presents some balance problems: Everyone can get this, and it defends against everything. +-- consider flat damage armour instead of %? (based on Cun/Dex) newTalent{ name = "Danger Sense", - type = {"cunning/survival", 4}, + type = {"cunning/survival", 3}, require = cuns_req4, points = 5, mode = "passive", - getTrigger = function(self, t) return self:combatTalentScale(t, 0.15, 0.40, 0.5) end, - cooldown = function(self, t) return 30 end, - no_npc_use = true, - getReduction = function(self, t) - return self:combatTalentLimit(t, 75, 25, 65) + cooldown = function(self, t) return math.ceil(self:combatStatLimit("wil", 10, 30, 20)) end, -- Limit > 10 + fixed_cooldown = true, + getTrigger = function(self, t) -- return % of life, minimum life fraction + return self:combatTalentLimit(t, 0.15, 0.5, 0.3), 0.25 + end, +-- no_npc_use = true, + getReduction = function(self, t) -- depends on both Dex and Cun to prevent being too useful to Berserkers, Oozemancers, Mages ... + return self:combatLimit((self:getDex() + self:getCun()-20)*self:getTalentLevel(t), 75, 5, 26, 55, 1170) -- Limit < 75%, = 5% @ TL1.0 dex/cun = 10/36, ~= 41% @ TL6.5, dex/cun = 50/50, ~= 55% @ TL6.5, dex/cun = 100/100 end, callbackOnHit = function(self, t, cb, src) - if self.life > self.max_life*t.getTrigger(self,t) and self.life - cb.value <= self.max_life*t.getTrigger(self,t) and not self:isTalentCoolingDown(t) then - self:setEffect("EFF_DANGER_SENSE", 1, {reduce = t.getReduction(self, t)}) - local eff = self:hasEffect("EFF_DANGER_SENSE") - eff.dur = eff.dur - 1 - cb.value = cb.value * (100-t.getReduction(self, t)) / 100 - self:startTalentCooldown(t) + if not self:isTalentCoolingDown(t) then + --game.log("#GREY#%s: Checking Danger Sense: dam=%0.2f, life = %d, max_life=%d", self.name, cb.value, self.life, self.max_life) + local dam_trigger, life_trigger = t.getTrigger(self, t) + dam_trigger, life_trigger = dam_trigger*self.life, life_trigger*self.max_life + if cb.value > dam_trigger or self.life - cb.value < life_trigger then + --game.log("#GREY#%s: Danger Sense Triggered! dam=%0.2f, d_t=%0.2f, l_t=%0.2f", self.name, cb.value, dam_trigger, life_trigger) + local reduce = t.getReduction(self, t) + self:setEffect("EFF_DANGER_SENSE", 1, {reduce = reduce}) + local eff = self:hasEffect("EFF_DANGER_SENSE") + eff.dur = eff.dur - 1 + cb.value = cb.value * (100-reduce) / 100 + self:startTalentCooldown(t) + return cb.value + end end - return cb.value end, info = function(self, t) - return ([[You react quickly when in danger - on falling below %d%% life, all damage you take from the attack and all further damage that turn is reduced by %d%%. -This talent has a cooldown.]]): - format(t.getTrigger(self,t)*100, t.getReduction(self,t) ) + local life_fct, life_min = t.getTrigger(self, t) + return ([[You have an enhanced sense of self preservation, and use keen intuition and fast reflexes to react quickly when you feel your life is in peril. + If damage would deal more than %d%% of your current life (ignoring any negative life limit), or would reduce your life below 25%% of maximum (%d), you avoid %d%% of that damage and any additional damage received later in the same turn. + The damage avoidance improves with Cunning and Dexterity. + This talent has a cooldown that decreases with your Willpower.]]): + format(life_fct*100, life_min*self.max_life, t.getReduction(self,t) ) end, } +-- track to detect and device master to disarm? newTalent{ name = "Disarm Trap", type = {"base/class", 1}, @@ -137,6 +152,7 @@ newTalent{ points = 1, range = 1, message = false, + no_break_stealth = true, image = "talents/trap_priming.png", target = {type="hit", range=1, nowarning=true, immediate_keys=true, no_lock=false}, action = function(self, t) @@ -151,10 +167,13 @@ newTalent{ dir = util.getDir(x, y, self.x, self.y) x, y = util.coordAddDir(self.x, self.y, dir) print("Requesting disarm trap", self.name, t.id, x, y) + local tl = self:getTalentLevel(self.T_DEVICE_MASTERY) local trap = self:detectTrap(nil, x, y) if trap then print("Found trap", trap.name, x, y) - if (x == self.x and y == self.y) or self:canMove(x, y) then + if tl < 3 then + game.logPlayer(self, "#CADET_BLUE#You need more skill to disarm traps.") + elseif (x == self.x and y == self.y) or self:canMove(x, y) then local px, py = self.x, self.y self:move(x, y, true) -- temporarily move to make sure trap can trigger properly trap:trigger(self.x, self.y, self) -- then attempt to disarm the trap, which may trigger it @@ -171,8 +190,8 @@ newTalent{ info = function(self, t) local ths = self:getTalentFromId(self.T_DEVICE_MASTERY) local power = ths.trapPower(self,ths) - return ([[You search an adjacent grid for a hidden trap (%d detection 'power') and attempt to disarm it (%d disarm 'power'). - To disarm a trap, you must be able to enter its grid to manipulate it, even though you stay in your current location. - Success depends on your skill in the %s talent and your Cunning, and failing to disarm a trap may trigger it.]]):format(power, power + (self:attr("disarm_bonus") or 0), ths.name) + return ([[You search an adjacent grid for a hidden trap (%d detection 'power') and disarm it (%d disarm 'power') if possible. + To disarm, you must have at least 3 talent levels in %s and be able to enter the trap's grid to manipulate it, though you stay in your current location. A failed attempt to disarm a trap may trigger it. + Your skill improves with your your Cunning.]]):format(power, power + (self:attr("disarm_bonus") or 0), ths.name) end, } \ No newline at end of file diff --git a/game/modules/tome/data/talents/cunning/traps.lua b/game/modules/tome/data/talents/cunning/traps.lua index 3392ce8e489671ce2ee23cfc1dfdcc7e705e9329..1e32a397863eb1c6461141a9139d818113bc96f8 100644 --- a/game/modules/tome/data/talents/cunning/traps.lua +++ b/game/modules/tome/data/talents/cunning/traps.lua @@ -386,7 +386,7 @@ end --- Create and place a bladestorm construct (Bladestorm Trap) summon_bladestorm = function(self, target, duration, x, y, scale ) local m = mod.class.NPC.new{ - type = "construct", subtype = "trap", + type = "construct", subtype = "mechanical", display = "^", color=colors.BROWN, image = "npc/trap_bladestorm_swish_01.png", name = "bladestorm construct", faction = self.faction, desc = [[A lethal contraption of whirling blades.]], @@ -429,9 +429,9 @@ summon_bladestorm = function(self, target, duration, x, y, scale ) never_move = 1, cant_be_moved = 1, negative_status_effect_immune = 1, - - combat_armor = math.floor(self.level*.75 + 10), combat_def = self.level * 2, - resists = {all = self:combatTalentLimit(self:getTalentLevel(self.T_TRAP_MASTERY), 70, 40, 50)}, + combat_armor = math.floor(2*self.level^.75), + combat_def = self:getCun()^.75, + resists = {all = self:combatStatLimit("cun", 70, 25, 50)}, negative_status_immune = 1, no_drops = 1, @@ -1272,6 +1272,7 @@ newTalent{ local trap = trapping_CreateTrap(self, t, nil, { type = "physical", name = "bladestorm trap", color=colors.BLACK, image = "trap/trap_bladestorm_01.png", dur = dur, + stamina = t.stamina, triggered = function(self, x, y, who) local tx, ty = util.findFreeGrid(x, y, 1, true, {[engine.Map.ACTOR]=true}) -- don't activate without room if not tx or not ty then return nil end @@ -1469,7 +1470,7 @@ newTalent{ require = cuns_req_unlock, unlock_talent = function(self, t) return self.player or self.level > 15, "You have learned how to create Freezing traps!" end, no_unlearn_last = true, - cooldown = 10, + cooldown = 12, stamina = 12, tactical = { ATTACKAREA = { COLD = 1.5 }, ESCAPE = {pin = 1}, CLOSEIN = {pin = 1}}, requires_target = function(self, t) return self.trap_primed == t.id end, @@ -1509,7 +1510,7 @@ newTalent{ -- Add a lasting map effect game.level.map:addEffect(self, x, y, 5, - engine.DamageType.ICE, self.dam/2, + engine.DamageType.ICE, self.dam/3, 2, 5, nil, {type="ice_vapour"}, @@ -1536,7 +1537,7 @@ newTalent{ end, short_info = function(self, t) local dam = damDesc(self, DamageType.COLD, t.getDamage(self, t)) - return ([[Explodes (radius 2): Deals %0.2f cold damage and pins for 3 turns. Area freezes (%0.2f cold damage, 25%% freeze chance) for 5 turns.]]):format(dam, dam/2) + return ([[Explodes (radius 2): Deals %0.2f cold damage and pins for 3 turns. Area freezes (%0.2f cold damage, 25%% freeze chance) for 5 turns.]]):format(dam, dam/3) end, info = function(self, t) local dam = damDesc(self, DamageType.COLD, t.getDamage(self, t)) @@ -1544,7 +1545,7 @@ newTalent{ return ([[Lay a trap that explodes into a radius 2 cloud of freezing vapour when triggered. Foes take %0.2f cold damage and are pinned for 3 turns. The freezing vapour persists for 5 turns, dealing %0.2f cold damage each turn to foes with a 25%% chance to freeze. This trap can use a primed trigger and a high level lure can trigger it.%s]]): - format(dam, dam/2, instant) + format(dam, dam/3, instant) end, } @@ -1651,15 +1652,16 @@ newTalent{ type_no_req = true, require = cuns_req_unlock, no_unlearn_last = true, - cooldown = 15, - stamina = 12, + cooldown = 20, + stamina = 15, tactical = { ATTACKAREA = { temporal = 2 }, CLOSEIN = 1.5, ESCAPE = 1.5}, is_spell = true, range = trap_range, speed = trap_speed, no_break_stealth = trap_stealth, - getDamage = function(self, t) return 20 + self:combatStatScale("mag", 5, 50) * self:callTalent(self.T_TRAP_MASTERY,"getTrapMastery") / 40 end, - getDuration = function(self, t) return math.floor(self:callTalent(self.T_TRAP_MASTERY, "getDuration")/2) end, + message = "@Source@ deploys a warped device.", + getDamage = function(self, t) return 10 + trap_effectiveness(self, t, "mag")*10 end, + getDuration = function(self, t) return math.floor(self:combatTalentLimit(self:getTalentLevel(self.T_TRAP_MASTERY), 20/2, 3, 5)) end, target = function(self, t) return {type="ball", range=self:getTalentRange(t), friendlyfire=false, radius=5} end, -- for AI action = function(self, t) local tg = {type="bolt", nowarning=true, range=self:getTalentRange(t), nolock=true, simple_dir_request=true, _allow_on_target=true} @@ -1673,6 +1675,7 @@ newTalent{ subtype = "arcane", name = "gravitic trap", color=colors.LIGHT_RED, image = "invis.png", embed_particles = {{name="wormhole", rad=5, args={image="shockbolt/trap/trap_gravitic", speed=1}}}, dam = dam, + check_hit = math.max(self:combatAttack(), self:combatSpellpower()), tg = tg, stamina = t.stamina, dur = dur, @@ -1693,7 +1696,7 @@ newTalent{ end) end if target then - self.turns_to_act = math.min(self.dur, self.gravity) self.gravity = 0 + self.turns_to_act = math.min(self.dur, self.gravity) self.gravity, self.stamina = 0, 0 else -- recharge gravity level self.gravity = self.gravity + 1 return @@ -1719,7 +1722,7 @@ newTalent{ if self.summoner then self.summoner.__project_source = self end engine.DamageType:get(engine.DamageType.TEMPORAL).projector(self.summoner, target.x, target.y, engine.DamageType.TEMPORAL, self.dam) if self.summoner then self.summoner.__project_source = nil end - if target:canBe("knockback") then + if self:checkHitOld(self.check_hit, target:combatPhysicalResist()) and target:canBe("knockback") then target:pull(self.x, self.y, 1) if target.x ~= ox or target.y ~= oy then target:logCombat(self, "#LIGHT_STEEL_BLUE##Target# pulls #Source# in!") @@ -1727,6 +1730,8 @@ newTalent{ self:disarm(self.x, self.y, target) end end + else + target:logCombat(self, "#LIGHT_STEEL_BLUE##Source# resists the pull of #Target#!") end end end) @@ -1751,9 +1756,10 @@ newTalent{ format(t.getDuration(self,t), damDesc(self, engine.DamageType.TEMPORAL, t.getDamage(self, t))) end, info = function(self, t) - return ([[Lay a trap that creates a radius 5 gravitic anomaly when triggered by foes approaching within range 1. Each turn, the anomaly deals %0.2f temporal damage (based on your Magic) to foes whle pulling them towards its center. - Each anomaly lasts %d turns (up to the amount of time since the last anomaly dissipated, based on your Trap Mastery skill), and the trap may trigger more than once, though it requires at least 2 turns to recharge between anomalies. -This trap does not require advanced preparation to use.]]): + return ([[Lay a trap that creates a radius 5 gravitic anomaly when triggered by foes approaching within range 1. Each turn, the anomaly deals %0.2f temporal damage (based on your Magic) to foes whle pulling them towards its center (chance increases with your combat accuracy or spell power, whichever is higher). + Each anomaly lasts %d turns (up to the amount of time since the last anomaly dissipated, based on your Trap Mastery skill). + The trap may trigger more than once, but requires at least 2 turns to recharge between activations. +This design does not require advanced preparation to use.]]): format(damDesc(self, engine.DamageType.TEMPORAL, t.getDamage(self, t)), t.getDuration(self,t)) end, } diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua index 5560b216be6c7eb8397ad497d36c7c3e934c7ab6..8927fbd51fed944109d47cced145f867fab4d241 100644 --- a/game/modules/tome/data/talents/misc/npcs.lua +++ b/game/modules/tome/data/talents/misc/npcs.lua @@ -3249,4 +3249,72 @@ newTalent{ The detection power increases with your Cunning.]]): format(t.seePower(self,t), t.seePower(self,t)) end, +} + +newTalent{ + name = "Precision", +-- type = {"technique/dualweapon-training", 3}, + type = {"technique/other", 1}, + mode = "sustained", + points = 5, + require = techs_dex_req3, + no_energy = true, + cooldown = 10, + sustain_stamina = 20, + tactical = { BUFF = 2 }, + on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end, + getApr = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getDex(), 4, 0, 25, 500, 0.75) end, + activate = function(self, t) + local weapon, offweapon = self:hasDualWeapon() + if not weapon then + game.logPlayer(self, "You cannot use Precision without dual wielding!") + return nil + end + + return { + apr = self:addTemporaryValue("combat_apr",t.getApr(self, t)), + } + end, + deactivate = function(self, t, p) + self:removeTemporaryValue("combat_apr", p.apr) + return true + end, + info = function(self, t) + return ([[You have learned to hit the right spot, increasing your armor penetration by %d when dual wielding. + The Armour penetration bonus will increase with your Dexterity.]]):format(t.getApr(self, t)) + end, +} + +newTalent{ + name = "Momentum", +-- type = {"technique/dualweapon-training", 4}, + type = {"technique/other", 1}, + mode = "sustained", + points = 5, + cooldown = 30, + sustain_stamina = 50, + require = techs_dex_req4, + tactical = { BUFF = 2 }, + on_pre_use = function(self, t, silent) if self:hasArcheryWeapon() or not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two melee weapons to use this talent.") end return false end return true end, + getSpeed = function(self, t) return self:combatTalentScale(t, 0.11, 0.40, 0.75) end, + activate = function(self, t) + local weapon, offweapon = self:hasDualWeapon() + if not weapon then + game.logPlayer(self, "You cannot use Momentum without dual wielding melee weapons!") + return nil + end + + return { + combat_physspeed = self:addTemporaryValue("combat_physspeed", t.getSpeed(self, t)), + stamina_regen = self:addTemporaryValue("stamina_regen", -6), + } + end, + deactivate = function(self, t, p) + self:removeTemporaryValue("combat_physspeed", p.combat_physspeed) + self:removeTemporaryValue("stamina_regen", p.stamina_regen) + return true + end, + info = function(self, t) + return ([[When dual wielding, increases attack speed by %d%%, but drains stamina quickly (-6 stamina/turn).]]):format(t.getSpeed(self, t)*100) + end, } \ No newline at end of file diff --git a/game/modules/tome/data/talents/techniques/assassination.lua b/game/modules/tome/data/talents/techniques/assassination.lua index ee5ac70ae616b2425f3b54e355099173ce24f779..430618c017fe5b3a5182754f1f406623998ec817 100644 --- a/game/modules/tome/data/talents/techniques/assassination.lua +++ b/game/modules/tome/data/talents/techniques/assassination.lua @@ -66,23 +66,24 @@ newTalent{ end end - if target.dead and self:knowTalent(self.T_STEALTH) and not self:isTalentActive(self.T_STEALTH) then + if target.dead and self:knowTalent(self.T_STEALTH) then game:onTickEnd(function() - self.hide_chance = 1000 - self.talents_cd["T_STEALTH"] = 0 - self:forceUseTalent(self.T_STEALTH, {ignore_energy=true, silent = true}) - self.hide_chance = nil + if not self:isTalentActive(self.T_STEALTH) then + self.hide_chance = 1000 + self.talents_cd["T_STEALTH"] = 0 + game.logSeen(self, "#GREY#%s slips into shadow.", self.name:capitalize()) + self:forceUseTalent(self.T_STEALTH, {ignore_energy=true, silent = true}) + self.hide_chance = nil + end end) end - return true - end, info = function(self, t) dam = t.getDamage(self,t)*100 perc = t.getPercent(self,t)*100 - return ([[Attempt to finish off a wounded enemy, striking them with both weapons for %d%% weapon damage, plus additional physical damage for each hit that lands equal to %d%% of their missing life (divided by rank: from 1 (critter) to 5 (elite boss)). - A target brought below 20%% of its maximum life may be instantly slain, which you may take advantage of to slip back into stealth.]]): + return ([[Attempt to finish off a wounded enemy, striking them with both weapons for %d%% weapon damage, plus additional physical damage for each hit that lands equal to %d%% of their missing life (divided by rank: from 1 (critter) to 5 (elite boss)). A target brought below 20%% of its maximum life may be instantly slain. + You may take advantage of finishing your foe this way to activate stealth (if known).]]): format(dam, perc) end, } @@ -107,7 +108,8 @@ newTalent{ local radius = self:getTalentRadius(t) local duration = t.getDuration(self,t) return ([[When you exit stealth, you reveal yourself dramatically, intimidating foes around you. - All enemies that witness you leaving stealth within radius %d will be stricken with terror, which randomly inflicts stun, slow (40%% power), or confusion (50%% power) for %d turns.]]) + All foes radius %d that witness you leaving stealth will be stricken with terror, which randomly inflicts stun, slow (40%% power), or confusion (50%% power) for %d turns. + The chance to terrorize improves with your combat accuracy.]]) :format(radius, duration) end, } diff --git a/game/modules/tome/data/talents/techniques/combat-training.lua b/game/modules/tome/data/talents/techniques/combat-training.lua index 35b506249e930323117e137e64a324476a832252..12f21431d2f7a859542048c77161a39900e7d5a7 100644 --- a/game/modules/tome/data/talents/techniques/combat-training.lua +++ b/game/modules/tome/data/talents/techniques/combat-training.lua @@ -99,12 +99,12 @@ newTalent{ end, } +-- could fold this into regular armour training to reduce investment newTalent{ name = "Light Armour Training", type = {"technique/combat-training", 1}, mode = "passive", levelup_screen_break_line = true, - no_unlearn_last = true, points = 5, require = {stat = {dex = function(level) return 16 + (level + 2) * (level - 1) end}}, getArmorHardiness = function(self, t) @@ -112,9 +112,15 @@ newTalent{ end, getDefense = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getDex(), 4, 0, 45.7, 500) end, getPercentageDefense = function(self, t) return self:combatTalentLimit(t, 75, 15, 45) end, - getStamina = function(self, t) return self:combatTalentLimit(t, 5, 1.5, 3.5) end, + getFatigue = function(self, t, fake) + -- Note: drakeskin body armour @ 8% + drakeskin leather cap @ 5% + drakeskin leather boots @ 5% = 18% + if fake or self:hasLightArmor() then + return self:combatTalentLimit(t, 50, 5, 18) + else return 0 + end + end, callbackOnMove = function(self, t, moved, force, ox, oy) - if not moved or force or (ox == self.x and oy == self.y) then return end + if not moved or force or (ox == self.x and oy == self.y) or not self:hasLightArmor() then return end local nb_foes = 0 local add_if_visible_enemy = function(x, y) @@ -127,14 +133,14 @@ newTalent{ self:project(adjacent_tg, self.x, self.y, add_if_visible_enemy) if nb_foes > 0 then - self:setEffect(self.EFF_MOBILE_DEFENCE, 2, {power=t.getDefense(self,t), stamina=t.getStamina(self,t)}) + self:setEffect(self.EFF_MOBILE_DEFENCE, 2, {power=t.getDefense(self,t), stamina=0}) end end, info = function(self, t) - return ([[Whilst wearing leather or lighter armour, you gain %d Defense and %d%% Armour hardiness. - In addition, stepping into a tile adjacent to a visible enemy will increase your stamina regeneration by %0.1f and total Defense by %d%% for 2 turns. + return ([[You learn to maintain your agility and manage your combat posture while wearing light armour. When wearing armour no heavier than leather, you gain %d Defense, %d%% Armour hardiness, and %d%% reduced Fatigue. + In addition, when you step adjacent to a (visible) enemy, you use the juxtaposition to increase your total Defense by %d%% for 2 turns. The Defense bonus scales with your Dexterity.]]): - format(t.getDefense(self,t), t.getArmorHardiness(self,t), t.getStamina(self,t), t.getPercentageDefense(self,t)) + format(t.getDefense(self,t), t.getArmorHardiness(self,t), t.getFatigue(self, t, true), t.getPercentageDefense(self,t)) end, } diff --git a/game/modules/tome/data/talents/techniques/dualweapon.lua b/game/modules/tome/data/talents/techniques/dualweapon.lua index d22d3c0376584628b538545c7b6dbe201c2e2347..5e3e43c48df456900c0ce45fa23bfa8128066c21 100644 --- a/game/modules/tome/data/talents/techniques/dualweapon.lua +++ b/game/modules/tome/data/talents/techniques/dualweapon.lua @@ -17,6 +17,9 @@ -- Nicolas Casalini "DarkGod" -- darkgod@te4.org +------------------------------------------------------ +-- General Dual Weapon Techniques +------------------------------------------------------ newTalent{ name = "Dual Weapon Training", type = {"technique/dualweapon-training", 1}, @@ -33,7 +36,6 @@ newTalent{ end, } - newTalent{ -- Note: classes: Temporal Warden, Rogue, Shadowblade, Marauder name = "Dual Weapon Defense", type = {"technique/dualweapon-training", 2}, @@ -43,10 +45,10 @@ newTalent{ -- Note: classes: Temporal Warden, Rogue, Shadowblade, Marauder -- called by _M:combatDefenseBase in mod.class.interface.Combat.lua getDefense = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getDex(), 4, 0, 45.7, 500) end, getDeflectChance = function(self, t) --Chance to parry with an offhand weapon - return self:combatLimit(self:getTalentLevel(t)*self:getDex(), 100, 15, 20, 60, 250) -- ~67% at TL 6.5, 55 dex + return self:combatLimit(self:getTalentLevel(t)*self:getDex(), 100, 15, 20, 60, 250) -- ~68% at TL 6.5, 55 dex end, getDeflectPercent = function(self, t) -- Percent of offhand weapon damage used to deflect - return math.max(0, self:combatTalentLimit(self:getTalentLevel(t), 100, 10, 50)) + return math.max(0, self:combatTalentLimit(t, 100, 15, 50)) end, getDamageChange = function(self, t, fake) local dam,_,weapon = 0,self:hasDualWeapon() @@ -58,12 +60,13 @@ newTalent{ -- Note: classes: Temporal Warden, Rogue, Shadowblade, Marauder end, -- deflect count handled in physical effect "PARRY" in mod.data.timed_effects.physical.lua getDeflects = function(self, t, fake) - if not self:hasDualWeapon() and not fake then return 0 end - return self:combatStatScale("cun", 1, 2.25) + if fake or self:hasDualWeapon() then + return self:combatStatScale("cun", 1, 2.25) + else return 0 + end end, callbackOnActBase = function(self, t) -- refresh the buff each turn in mod.class.Actor.lua _M:actBase local mh, oh = self:hasDualWeapon() --- if self:hasDualWeapon() then if (mh and oh) and oh.subtype ~= "mindstar" then self:setEffect(self.EFF_PARRY,1,{chance=t.getDeflectChance(self, t), dam=t.getDamageChange(self, t), deflects=t.getDeflects(self, t)}) end @@ -80,74 +83,128 @@ newTalent{ -- Note: classes: Temporal Warden, Rogue, Shadowblade, Marauder end, } +-- flat armor vs only on_hit damage, sustain may be toggled to possibly reflect damage back to defender newTalent{ - name = "Precision", + name = "Close Combat Management", type = {"technique/dualweapon-training", 3}, + image = "talents/counter_attack.png", mode = "sustained", points = 5, require = techs_dex_req3, no_energy = true, - cooldown = 10, - sustain_stamina = 20, - tactical = { BUFF = 2 }, - on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end, - getApr = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getDex(), 4, 0, 25, 500, 0.75) end, + sustain_stamina = 10, + tactical = { BUFF = 1 }, + passives = function(self, t) + self.turn_procs.reflectArmour = nil + end, + on_pre_use = function(self, t, silent) + if not self:hasDualWeapon() then + if not silent then game.logPlayer(self, "You must dual wield to use this talent.") end + return false + end + return true + end, + getReflectArmour = function(self, t) + return self:combatScale(self:getTalentLevel(t) * self:getDex(25, true), 0, 0, 35, 125, 0.5, 0, 1) + end, + getPercent = function(self, t) return math.max(0, self:combatTalentLimit(t, 50, 10, 30)) end, + reflectArmour = function(self, t, combat) -- called in Combat.attackTargetHitProcs + local tp_ra = self.turn_procs.reflectArmour + if not tp_ra then + local mh, oh = self:hasDualWeapon() + tp_ra = {fa=t.getReflectArmour(self, t), pct=self:isTalentActive(t.id) and t.getPercent(self, t) or 0} + if mh then + if mh.subtype ~= "mindstar" then tp_ra.mh = mh end + if oh.subtype ~= "mindstar" then tp_ra.oh = oh end + end + self.turn_procs.reflectArmour = tp_ra + end + if combat == (tp_ra.mh and tp_ra.mh.combat) or combat == (tp_ra.oh and tp_ra.oh.combat) then + return tp_ra.fa, tp_ra.pct + else return 0, 0 + end + end, activate = function(self, t) + self.turn_procs.reflectArmour = nil local weapon, offweapon = self:hasDualWeapon() - if not weapon then - game.logPlayer(self, "You cannot use Precision without dual wielding!") + if not (weapon and offweapon) then + game.logPlayer(self, "You must dual wield to manage contact with your target!") return nil end - - return { - apr = self:addTemporaryValue("combat_apr",t.getApr(self, t)), - } + return {} end, deactivate = function(self, t, p) - self:removeTemporaryValue("combat_apr", p.apr) + self.turn_procs.reflectArmour = nil return true end, info = function(self, t) - return ([[You have learned to hit the right spot, increasing your armor penetration by %d when dual wielding. - The Armour penetration bonus will increase with your Dexterity.]]):format(t.getApr(self, t)) + return ([[You have learned how to carefully manage contact between you and your opponent. + When striking in melee with your dual wielded weapons, you automatically avoid up to %d damage dealt to you from each of your target's on hit effects. This improves with your Dexterity, but is not possible with mindstars. + In addition, while this talent is active, you redirect %d%% of the damage you avoid this way back to your target.]]): + format(t.getReflectArmour(self, t), t.getPercent(self, t)) end, } +--- Attack mainhand plus unarmed, with chance to confuse newTalent{ - name = "Momentum", - type = {"technique/dualweapon-training", 4}, - mode = "sustained", + name = "Offhand Jab", + type = {"technique/dualweapon-training", 3}, + image = "talents/golem_crush.png", points = 5, - cooldown = 30, - sustain_stamina = 50, - require = techs_dex_req4, - tactical = { BUFF = 2 }, - on_pre_use = function(self, t, silent) if self:hasArcheryWeapon() or not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two melee weapons to use this talent.") end return false end return true end, - getSpeed = function(self, t) return self:combatTalentScale(t, 0.11, 0.40, 0.75) end, - activate = function(self, t) + random_ego = "attack", + cooldown = 6, + stamina = 5, + require = techs_dex_req3, + requires_target = true, + tactical = { ATTACK = { weapon = 2 }, DISABLE = { confusion = 1.5 } }, + on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end, + getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1, 1.5) end, + getConfusePower = function(self, t) return self:combatTalentLimit(t, 50, 25, 40) end, + getConfuseDuration = function(self, t) return math.floor(self:combatTalentScale(t, 2, 4)) end, + on_learn = function(self, t) + self:attr("show_gloves_combat", 1) + end, + on_unlearn = function(self, t) + self:attr("show_gloves_combat", -1) + end, + action = function(self, t) local weapon, offweapon = self:hasDualWeapon() if not weapon then - game.logPlayer(self, "You cannot use Momentum without dual wielding melee weapons!") + game.logPlayer(self, "You must dual wield to perform an Offhand Jab!") return nil end + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y or not target then return nil end + if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end + + local dam_mult = t.getDamage(self, t) - return { - combat_physspeed = self:addTemporaryValue("combat_physspeed", t.getSpeed(self, t)), - stamina_regen = self:addTemporaryValue("stamina_regen", -6), - } - end, - deactivate = function(self, t, p) - self:removeTemporaryValue("combat_physspeed", p.combat_physspeed) - self:removeTemporaryValue("stamina_regen", p.stamina_regen) + -- First attack with mainhand + local speed, hit = self:attackTargetWith(target, weapon.combat, nil, dam_mult) + + -- Then attack unarmed + speed, hit = self:attackTargetWith(target, self:getObjectCombat(nil, "barehand"), nil, dam_mult+1.25) + if hit then + if target:canBe("confusion") then + target:setEffect(target.EFF_CONFUSED, t.getConfuseDuration(self, t), {apply_power=self:combatAttack(), power=t.getConfusePower(self, t)}) + else + game.logSeen(target, "%s resists the surprise strike!", target.name:capitalize()) + end + end return true end, info = function(self, t) - return ([[When dual wielding, increases attack speed by %d%%, but drains stamina quickly (-6 stamina/turn).]]):format(t.getSpeed(self, t)*100) + local dam = 100 * t.getDamage(self, t) + return ([[With a quick shift of your momentum, you execute a surprise unarmed strike in place of your normal offhand attack. + This allows you to attack with your mainhand weapon for %d%% damage and unarmed for %d%% damage. If the unarmed attack hits, the target is confused (%d%% power) for %d turns. + The chance to confuse increases with your Accuracy.]]) + :format(dam, dam*1.25, t.getConfusePower(self, t), t.getConfuseDuration(self, t)) end, } ------------------------------------------------------ --- Attacks +-- Primary Attacks ------------------------------------------------------ newTalent{ name = "Dual Strike", @@ -241,7 +298,7 @@ newTalent{ getDamage = function (self, t) return self:combatTalentWeaponDamage(t, 1.0, 1.7) end, getCrit = function(self, t) return self:combatTalentLimit(t, 50, 10, 30) end, target = function(self, t) return {type="bolt", range=self:getTalentRange(t)} end, - range = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 3, 5)) end, + range = function(self, t) return math.floor(self:combatTalentLimit(t, 10, 3, 5.5)) end, requires_target = true, tactical = { ATTACK = { weapon = 2 }, CLOSEIN = 2 }, on_pre_use = function(self, t, silent) @@ -291,8 +348,6 @@ newTalent{ self.combat_critical_power = nil self.combat_critical_power = critstore - - return true end, info = function(self, t) @@ -312,13 +367,13 @@ newTalent{ stamina = 30, require = techs_dex_req4, tactical = { ATTACKAREA = { weapon = 2 }, CLOSEIN = 1.5 }, - range = function(self, t) if self:getTalentLevel(t) >=3 then return 3 else return 2 end end, + range = function(self, t) return math.floor(self:combatTalentLimit(t, 6, 2, 4)) end, radius = 1, requires_target = true, target = function(self, t) return {type="beam", range=self:getTalentRange(t), talent=t } end, - getDamage = function (self, t) return self:combatTalentWeaponDamage(t, 1.0, 1.6) end, + getDamage = function (self, t) return self:combatTalentWeaponDamage(t, 0.6, 1.1) end, proj_speed = 20, --not really a projectile, so make this super fast on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then @@ -332,45 +387,72 @@ newTalent{ end, action = function(self, t) local tg = self:getTalentTarget(t) - local x, y = self:getTarget(tg) - if not x or not y then return nil end + local x, y, target = self:getTarget(tg) + if not (x and y) then return nil end + if core.fov.distance(self.x, self.y, x, y) > tg.range or not self:hasLOS(x, y) then + game.logPlayer(self, "The target location must be within range and within view.") + return nil + end local _ _, x, y = self:canProject(tg, x, y) - if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) or not self:hasLOS(x, y) then return nil end - if target or game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move", self) then return nil end + if not (x and y) or not self:hasLOS(x, y) then return nil end + -- make sure the grid location is valid + local mx, my, grids = util.findFreeGrid(x, y, 1, true, {[Map.ACTOR]=true}) + if mx and my then + if core.fov.distance(self.x, self.y, mx, my) > tg.range or not self:hasLOS(mx, my) then -- not valid, check other free grids + mx, my = nil, nil + for i, grid in ipairs(grids) do + if core.fov.distance(self.x, self.y, grid[1], grid[2]) <= tg.range and self:hasLOS(grid[1], grid[2]) then + mx, my = grid[1], grid[2] + break + end + end + end + end + if not (mx and my) then + game.logPlayer(self, "There is no open space in which to land near there.") + return nil + end - self:projectile(tg, x, y, function(px, py, tg, self) - local aoe = {type="ball", radius=1, friendlyfire=true, selffire=false, talent=t, display={ } } - + game.logSeen(self, "%s becomes an unstoppable whirlwind of weapons!", self.name:capitalize()) + -- move the actor + self:move(mx, my, true) + + -- Create a high-speed projectile tracing a path to the destination that does the actual damage + local wwproj = self:projectile(tg, mx, my, function(px, py, tg, self, tmp_proj) + local aoe = {type="ball", radius=1, friendlyfire=false, selffire=false, talent=t, display={ } } + self.__project_source = nil + game.level.map:particleEmitter(px, py, 1, "meleestorm", {img="spinningwinds_red"}) self:project(aoe, px, py, function(tx, ty) local target = game.level.map(tx, ty, engine.Map.ACTOR) - if not target then return end - if target.turn_procs.whirlwind then return end - target.turn_procs.whirlwind = true - local oldlife = target.life - local hit = self:attackTarget(target, nil, t.getDamage(self,t), true) - local life_diff = oldlife - target.life - if life_diff > 0 and target:canBe('cut') then - target:setEffect(target.EFF_CUT, 5, {power=life_diff * 0.1, src=self, apply_power=self:combatPhysicalpower(), no_ct_effect=true}) + if not target or tmp_proj[target] or self.dead then return end + local mh, oh = self:hasDualWeapon() + if not (mh and oh) then return end + local dam = 0 + tmp_proj.targets = (tmp_proj.targets or 0) + 1 + tmp_proj[target] = true + local s, h, d = self:attackTargetWith(target, mh.combat, nil, tmp_proj.weapon_mult) + if h and d > 0 then dam = dam + d end + --print("\t WW mainhand damage", d) + s, h, d = self:attackTargetWith(target, oh.combat, nil, tmp_proj.weapon_mult) + if h and d > 0 then dam = dam + d end + --print("\t WW offhand damage", d) + if dam > 0 and target:canBe('cut') then + target:setEffect(target.EFF_CUT, 5, {power=dam*0.1, src=self, apply_power=self:combatPhysicalpower(), no_ct_effect=true}) end end) - end) - - local mx, my = util.findFreeGrid(x, y, 1, true, {[Map.ACTOR]=true}) - if not mx or not mx then - game.logSeen(self, "You cannot jump to that location.") - return nil end + ) + wwproj.tmp_proj.weapon_mult = t.getDamage(self, t) + wwproj.energy.value = game.energy_to_act -- make sure projectile begins moving immediately - self:move(mx, my, true) - return true end, info = function(self, t) local damage = t.getDamage(self, t) local range = self:getTalentRange(t) - return ([[You quickly move 2 tiles (or 3 at talent level 3 and above) to the target location, leaping around and over anyone in your path and striking any adjacent enemies with both weapons for %d%% weapon damage. All those struck will bleed for 50%% of the damage dealt over 5 turns.]]): - format(damage*100) + return ([[You quickly move up to %d tiles to arrive adjacent to a target location you can see, leaping around or over anyone in your way. During your movement, you attack all foes within one grid of your path with both weapons for %d%% weapon damage, causing those struck to bleed for 50%% of the damage dealt over 5 turns.]]): + format(range, damage*100) end, } diff --git a/game/modules/tome/data/talents/techniques/duelist.lua b/game/modules/tome/data/talents/techniques/duelist.lua index 587b19d2a4e2b2985bff7510b51fce7877269346..5259d2ff9593772a1fe05717bfb8b2898a96a4fe 100644 --- a/game/modules/tome/data/talents/techniques/duelist.lua +++ b/game/modules/tome/data/talents/techniques/duelist.lua @@ -27,18 +27,18 @@ newTalent{ points = 5, require = techs_dex_req1, mode = "passive", - getDeflectChance = function(self, t) return self:combatTalentLimit(t, 100, 20, 65) end, - getDeflectPercent = function(self, t) return self:combatTalentLimit(t, 100, 20, 50) end, - getDeflect = function(self, t, fake) - local dam,_,weapon = 0,self:hasDualWeapon() - if not weapon or weapon.subtype=="mindstar" and not fake then return 0 end - if weapon then - dam = self:combatDamage(weapon.combat) * self:getOffHandMult(weapon.combat) - end - return t.getDeflectPercent(self, t) * dam/100 + getDeflectChance = function(self, t) --Chance to parry with an offhand weapon + return self:combatLimit(self:getTalentLevel(t)*self:getDex(), 100, 15, 20, 60, 250) -- ~68% at TL 6.5, 55 dex + end, + getDeflectPercent = function(self, t) -- Percent of offhand weapon damage used to deflect + return math.max(0, self:combatTalentLimit(t, 100, 15, 50)) end, + -- deflect count handled in physical effect "PARRY" in mod.data.timed_effects.physical.lua getDeflects = function(self, t, fake) - return 2 + if fake or self:hasDualWeapon() then + return self:combatStatScale("cun", 2, 3) + else return 0 + end end, getDamageChange = function(self, t, fake) local dam,_,weapon = 0,self:hasDualWeapon() @@ -48,75 +48,67 @@ newTalent{ end return t.getDeflectPercent(self, t) * dam/100 end, - getoffmult = function(self,t) - return self:combatTalentLimit(t, 1, 0.65, 0.85)-- limit <100% - end, + getoffmult = function(self,t) return self:combatTalentLimit(t, 1, 0.6, 0.80) end, -- limit <100% callbackOnActBase = function(self, t) local mh, oh = self:hasDualWeapon() --- if self:hasDualWeapon() then if (mh and oh) and oh.subtype ~= "mindstar" then self:setEffect(self.EFF_PARRY,1,{chance=t.getDeflectChance(self, t), dam=t.getDamageChange(self, t), deflects=t.getDeflects(self, t), parry_ranged=true}) end end, on_unlearn = function(self, t) self:removeEffect(self.EFF_PARRY) end, info = function(self, t) - block = t.getDeflect(self,t) + mult = t.getoffmult(self,t)*100 + block = t.getDamageChange(self, t, true) chance = t.getDeflectChance(self,t) perc = t.getDeflectPercent(self,t) - mult = t.getoffmult(self,t)*100 - return ([[Up to %d times a turn, you have a %d%% chance to parry up to %d damage (%d%% of your offhand weapon damage) from a melee or ranged attack. - A successful parry reduces damage like armour (before any attack multipliers) and prevents critical strikes. It is difficult to parry attacks from unseen attackers and you cannot parry with a mindstar. - In addition, the damage dealt by your offhand weapon is increased to %d%%.]]): - format(t.getDeflects(self, t, true), chance, block, perc, mult) + return ([[Your offhand weapon damage penalty is reduced to %d%%. + Up to %0.1f times a turn, you have a %d%% chance to parry up to %d damage (%d%% of your offhand weapon damage) from a melee or ranged attack. The number of parries increases with your Cunning. (A fractional parry has a reduced chance to succeed.) + A successful parry reduces damage like armour (before any attack multipliers) and prevents critical strikes. It is difficult to parry attacks from unseen attackers and you cannot parry with a mindstar.]]): + format(100 - mult, t.getDeflects(self, t, true), chance, block, perc) end, } +local function do_tempo(self, t, src) -- handle Tempo defensive bonuses +--game.log("do_tempo called for %s (%s)", self.name, src and src.name) + if self.turn_procs.tempo then return end + local mh, oh = self:hasDualWeapon() + if mh and oh then + self:incStamina(t.getStamina(self,t)) + self.energy.value = self.energy.value + game.energy_to_act*t.getSpeed(self,t)/100 + local cooldown = self.talents_cd["T_FEINT"] or 0 + if cooldown > 0 then self.talents_cd["T_FEINT"] = math.max(cooldown - 1, 0) end + end + self.turn_procs.tempo = true +end + newTalent{ name = "Tempo", type = {"technique/duelist", 2}, require = techs_dex_req2, points = 5, mode = "passive", - getStamina = function(self, t) return self:combatTalentScale(t, 1, 4) end, - getSpeed = function(self, t) return self:combatTalentLimit(t, 50, 5, 15) end, - do_tempo = function(self, t, src) - if self.turn_procs.tempo then return end - self.turn_procs.tempo = true - self:incStamina(t.getStamina(self,t)) - local energy = (game.energy_to_act * t.getSpeed(self,t))/100 - self.energy.value = self.energy.value + energy - local cooldown = self.talents_cd["T_FEINT"] or 0 - if cooldown > 0 then - self.talents_cd["T_FEINT"] = math.max(cooldown - 1, 0) - end - end, - callbackOnMeleeMiss = function(self, t, src) - if self.turn_procs.tempo then return end - self.turn_procs.tempo = true - self:incStamina(t.getStamina(self,t)) - local energy = (game.energy_to_act * t.getSpeed(self,t))/100 - self.energy.value = self.energy.value + energy - local cooldown = self.talents_cd["T_FEINT"] or 0 - if cooldown > 0 then - self.talents_cd["T_FEINT"] = math.max(cooldown - 1, 0) - end - end, - callbackOnArcheryMiss = function(self, t, src) - if self.turn_procs.tempo then return end - self.turn_procs.tempo = true - self:incStamina(t.getStamina(self,t)) - local energy = (game.energy_to_act * t.getSpeed(self,t))/100 - self.energy.value = self.energy.value + energy - local cooldown = self.talents_cd["T_FEINT"] or 0 - if cooldown > 0 then - self.talents_cd["T_FEINT"] = math.max(cooldown - 1, 0) + getStamina = function(self, t) return self:combatTalentLimit(t, 15, 1, 4) end, -- Limit < 15 (effectively scales with actor speed) + getSpeed = function(self, t) return self:combatTalentLimit(t, 25, 5, 10) end, -- Limit < 25% of a turn gained + do_tempo = do_tempo, + callbackOnMeleeMiss = do_tempo, + callbackOnArcheryMiss = do_tempo, + -- handle offhand crit + callbackOnMeleeAttack = function(self, t, target, hitted, crit, weapon, damtype, mult, dam) + if crit and not self.turn_procs.tempo_attack then + local mh, oh = self:hasDualWeapon() + if oh and oh.combat == weapon then + self:incStamina(t.getStamina(self,t)) + end + self.turn_procs.tempo_attack = true end end, info = function(self, t) local sta = t.getStamina(self,t) local speed = t.getSpeed(self,t) - return ([[You attune yourself to the flow of battle. - Once per turn, on parrying, evading an attack, or avoiding damage (by Duelist's Focus), you instantly restore %0.1f stamina and gain %d%% of a turn.]]):format(sta, speed) + return ([[The flow of battle invigorates you, allowing you to press your advantage as the fight progresses. + Up to once each per turn, while dual wielding, you may: + Reposte -- If a melee or archery attack misses you, you parry it, or you avoid some of its damage (by Duelist's Focus), you instantly restore %0.1f stamina and gain %d%% of a turn. + Recover -- On performing a critical strike with your offhand weapon, you instantly restore %0.1f stamina.]]):format(sta, speed, sta) end, } @@ -130,7 +122,7 @@ newTalent{ cooldown = 30, no_energy = true, getChance = function(self, t) return self:combatTalentLimit(t, 25, 5, 15) end, - critResist = function(self, t) return self:combatTalentScale(t, 15, 50, 0.75) end, + critResist = function(self, t) return self:combatTalentScale(t, 5, 20, 0.75) end, on_pre_use = function(self, t, silent, fake) local armor = self:getInven("BODY") and self:getInven("BODY")[1] if armor and (armor.subtype == "heavy" or armor.subtype == "massive") then @@ -153,8 +145,8 @@ newTalent{ info = function(self, t) local chance = t.getChance(self,t) local crit = t.critResist(self,t) - return ([[Your reflexes are lightning quick, giving you a %d%% chance to entirely ignore incoming damage and causing all direct critical hits (physical, mental, spells) against you to have a %d%% lower Critical multiplier (but always do at least normal damage). -This requires unrestricted mobility, and so is not usable while in heavy or massive armor.]]) + return ([[Your reflexes are lightning quick, giving you a %d%% chance to entirely ignore incoming damage and causing all direct critical hits (physical, mental, spells) against you to have a %d%% lower critical multiplier (but always do at least normal damage). + This requires unrestricted mobility, and so is not usable when wearing heavy or massive armour.]]) :format(chance, crit) end, } @@ -227,8 +219,8 @@ newTalent{ local speed = t.getSpeedPenalty(self,t) local dur = t.getDuration(self,t) return ([[Make a cunning feint that tricks your target into swapping places with you. Taking advantage of the switch allows you to strike the target with a crippling blow, dealing %d%% offhand damage and reducing its melee, spellcasting, and mind speed by %d%% for %d turns. -The chance to cripple your target improves with your Accuracy, while the speed penalty increases with your Dexterity. -Tempo will reduce the cooldown of this talent by 1 turn each time it is triggered.]]): + The chance to cripple your target improves with your Accuracy, while the speed penalty increases with your Dexterity. + Tempo will reduce the cooldown of this talent by 1 turn each time it is triggered defensively.]]): format(dam*100, speed, dur) end, } \ No newline at end of file diff --git a/game/modules/tome/data/talents/techniques/mobility.lua b/game/modules/tome/data/talents/techniques/mobility.lua index 69b383e2d03f327f26396733c6f80a853a817484..cc36dbea4fc0e64603ce85606e6328e07ee02035 100644 --- a/game/modules/tome/data/talents/techniques/mobility.lua +++ b/game/modules/tome/data/talents/techniques/mobility.lua @@ -19,85 +19,120 @@ local Map = require "engine.Map" +local function mobility_pre_use(self, t, silent, fake) + if self:attr("never_move") then + if not silent then game.logPlayer(self, "You must be able to move to use %s!", t.name) end + return + elseif self:hasHeavyArmor() then + if not silent then game.logPlayer(self, "%s is not usable while wearing heavy armour.", t.name) end + return + end + return true +end + +local function mobility_stamina(self, t) + local cost = t.base_stamina or 0 + local eff = self:hasEffect(self.EFF_EXHAUSTION) + if eff then cost = cost*(1 + eff.fatigue/100) end + return cost +end + newTalent{ - name = "Disengage", + name = "Evasion", type = {"technique/mobility", 1}, + points = 5, require = techs_dex_req1, + random_ego = "defensive", + tactical = { ESCAPE = 2, DEFEND = 2 }, + cooldown = 30, + base_stamina = 20, + stamina = mobility_stamina, + no_energy = true, + getDur = function(self, t) return math.floor(self:combatTalentLimit(t, 30, 5, 9)) end, -- Limit < 30 + getChanceDef = function(self, t) + if self.perfect_evasion then return 100, 0 end + return self:combatLimit(5*self:getTalentLevel(t) + self:getDex(50,true), 50, 10, 10, 37.5, 75), + self:combatScale(self:getTalentLevel(t) * (self:getDex(50, true)), 0, 0, 55, 250, 0.75) + -- Limit evasion chance < 50%, defense bonus ~= 55 at level 50 + end, + speed = "combat", + action = function(self, t) + local dur = t.getDur(self,t) + local chance, def = t.getChanceDef(self,t) + self:setEffect(self.EFF_EVASION, dur, {chance=chance, defense = def}) + return true + end, + info = function(self, t) + local chance, def = t.getChanceDef(self,t) + return ([[Your quick wit and reflexes allow you to anticipate melee attacks, granting you a %d%% chance to completely evade them plus %d defense for %d turns. + The chance to evade and defense bonus increase with your Dexterity.]]): + format(chance, def,t.getDur(self,t)) + end, +} + +newTalent{ + name = "Disengage", + type = {"technique/mobility", 2}, + require = techs_dex_req2, points = 5, random_ego = "utility", cooldown = 10, - stamina = 12, + base_stamina = 12, + stamina = mobility_stamina, range = 7, - getSpeed = function(self, t) return self:combatTalentScale(t, 100, 250, 0.75) end, + getSpeed = function(self, t) return self:combatTalentScale(t, 100, 200, "log") end, getReload = function(self, t) return math.floor(self:combatTalentScale(t, 2, 10)) end, - tactical = { ESCAPE = 2 }, + tactical = { ESCAPE = 2, DEFEND = 0.5, AMMO = 0.5 }, requires_target = true, target = function(self, t) return {type="hit", range=self:getTalentRange(t)} end, - on_pre_use = function(self, t) - if self:attr("never_move") then return false end - return true - end, - getDist = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, + on_pre_use = mobility_pre_use, + getDist = function(self, t) return math.floor(self:combatTalentLimit(t, 11, 2, 5.5)) end, action = function(self, t) local tg = self:getTalentTarget(t) local x, y, target = self:getTarget(tg) if not target or not self:canProject(tg, x, y) then return nil end - self:knockback(target.x, target.y, t.getDist(self, t)) - - if self:getTalentLevel(t) >= 4 then - self:setEffect("EFF_AVOIDANCE", 1, {power=100}) - local eff = self:hasEffect("EFF_AVOIDANCE") - eff.dur = eff.dur - 1 + if not self:hasEffect(self.EFF_EVASION) then + self:forceUseTalent(self.T_EVASION, {ignore_cd=true, ignore_energy=true, silent=true, ignore_ressources=true, force_level=math.max(1, self:getTalentLevelRaw(self.T_EVASION))}) + local eff = self:hasEffect(self.EFF_EVASION) + if eff then eff.dur = 1 end end game:onTickEnd(function() self:setEffect(self.EFF_WILD_SPEED, 3, {power=t.getSpeed(self,t)}) end) - local weapon, ammo, offweapon = self:hasArcheryWeapon() - if weapon and ammo and not ammo.infinite then - ammo.combat.shots_left = math.min(ammo.combat.shots_left + t.getReload(self, t), ammo.combat.capacity) - game.logSeen(self, "%s reloads.", self.name:capitalize()) - end + self:knockback(target.x, target.y, t.getDist(self, t)) + self:reload() return true end, info = function(self, t) - return ([[Jump away %d grids from your target and gain a burst of speed on landing, increasing you movement speed by %d%% for 3 turns. - Any actions other than movement will end the speed boost. - At talent level 4 you avoid all attacks against you while disengaging. - If you have a quiver equip, you also take the time to reload %d shots.]]): - format(t.getDist(self, t), t.getSpeed(self,t), t.getReload(self,t)) + return ([[Jump back %d grids from your target and gain %d%% increased movement speed for 3 turns. + As part of this move, you will also gain 1 turn of Evasion for free and perform one turn's reloading of your equipped ammo after moving. + The extra speed ends if you take any actions other than movement. + This talent is not usable with heavy armor or while immobilized.]]): + format(t.getDist(self, t), t.getSpeed(self,t)) end, } newTalent { name = "Tumble", - type = {"technique/mobility", 2}, - require = techs_dex_req2, + type = {"technique/mobility", 3}, + require = techs_dex_req3, points = 5, random_ego = "attack", - cooldown = function(self, t) - return math.max(10 - self:getTalentLevel(t), 1) - end, + on_pre_use = mobility_pre_use, + cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 3, 10, 6)) end, no_energy = true, no_break_stealth = true, - tactical = { ESCAPE = 2 }, - stamina = function(self, t) - local eff = self:hasEffect(self.EFF_EXHAUSTION) - if eff and eff.charges then - return 15 + eff.charges*15 - else - return 15 - end - end, - range = function(self, t) - return math.floor(self:combatTalentScale(t, 2, 4)) - end, - getDuration = function(self, t) - return math.max(20 - self:getTalentLevel(t), 5) - end, + tactical = { CLOSEIN = 2 }, +-- tactical = { ESCAPE = 2, CLOSEIN = 2 }, -- update with AI + base_stamina = 10, + stamina = mobility_stamina, + getExhaustion = function(self, t) return self:combatTalentLimit(t, 20, 50, 35) end, + range = function(self, t) return math.floor(self:combatTalentScale(t, 2, 5, "log")) end, + getDuration = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 20, 10)) end, -- always >=2 turns higher than cooldown target = function(self, t) return {type="beam", range=self:getTalentRange(t), talent=t} end, @@ -116,90 +151,88 @@ newTalent { self:move(x, y, true) game:onTickEnd(function() - self:setEffect(self.EFF_EXHAUSTION, t.getDuration(self,t), { max_stacks=5 }) + self:setEffect(self.EFF_EXHAUSTION, t.getDuration(self,t), { fatigue = t.getExhaustion(self, t) }) end) return true end, info = function(self, t) - return ([[Move to a spot within range, bounding around, over, or through any enemies in the way. This can be used while pinned, and does not break stealth. - This quickly becomes exhausting to use, increasing the stamina cost by 15 for %d turns after use.]]):format(t.getDuration(self,t)) + return ([[In an extreme feat of agility, you move to a spot you can see within range, bounding around, over, or through any enemies in the way. + This talent cannot be used while wearing heavy armor, and leaves you exhausted. The exhaustion increases the cost of your activated Mobility talents by %d%% (stacking), but fades over %d turns.]]):format(t.getExhaustion(self, t), t.getDuration(self, t)) end } +-- Could let player/NPC select sensitivity to damage instead of simple toggle. newTalent { name = "Trained Reactions", - type = {"technique/mobility", 3}, + type = {"technique/mobility", 4}, mode = "sustained", points = 5, - cooldown = function(self, t) return 10 end, + require = techs_dex_req4, sustain_stamina = 10, no_energy = true, - require = techs_dex_req3, - tactical = { BUFF = 2 }, - activate = function(self, t) - return {} - end, - deactivate = function(self, t, p) + tactical = { DEFEND = 2 }, + pinImmune = function(self, t) return self:combatTalentLimit(t, 1, .17, .5) end, -- limit < 100% + passives = function(self, t, p) + self:talentTemporaryValue(p, "pin_immune", t.pinImmune(self, t)) + end, + on_pre_use = function(self, t, silent, fake) + if self:hasHeavyArmor() then + if not silent then game.logPlayer(self, "%s is not usable while wearing heavy armour.", t.name) end + return + end return true end, - getLifeTrigger = function(self, t) - return self:combatTalentLimit(t, 10, 40, 22) - end, - getReduction = function(self, t) - return (0.05 + (self:combatTalentLimit(t, 1, 0.15, 0.50) * self:combatLimit(self:combatDefense(), 1, 0.20, 10, 0.50, 50)))*100 - end, - callbackOnHit = function(self, t, cb, src) - if cb.value >= self.max_life * t.getLifeTrigger(self, t) * 0.01 and use_stamina(self, 10) then - -- Apply effect with duration 0. - self:setEffect("EFF_SKIRMISHER_DEFENSIVE_ROLL", 1, {reduce = t.getReduction(self, t)}) - local eff = self:hasEffect("EFF_SKIRMISHER_DEFENSIVE_ROLL") - eff.dur = eff.dur - 1 + getReduction = function(self, t, fake) -- get % reduction based on TL and defense + return self:combatTalentLimit(t, 0.9, 0.15, 0.7)*self:combatLimit(self:combatDefense(fake), 0.9, 0.20, 10, 0.70, 50) -- vs TL/def: 1/10 == ~3%, 1.3/10 == ~6%, 1.3/50 == ~19%, 6.5/50 == ~52%, 6.5/100 = ~59% + end, + getStamina = function(self, t) return 10*(1 + self:combatFatigue()/100)*math.max(0.1, self:combatTalentLimit(t, 1, 0.17, 0.8)) end, -- scales up with effectiveness (gets more efficient with TL) + getLifeTrigger = function(self, t, cur_stam) + local percent_hit = self:combatTalentLimit(t, 10, 35, 20) + local stam_cost = t.getStamina(self, t) + cur_stam = cur_stam or self:getStamina() + return percent_hit*util.bound(10*stam_cost/cur_stam, .5, 2) -- == 1 @ 10% stamina cost + end, + callbackOnTakeDamage = function(self, t, src, x, y, type, dam, state) + if state and (state.is_melee or state.is_archery) and not self:attr("encased_in_ice") and not self:attr("invulnerable") then + local stam, stam_cost = self:getStamina() + -- don't charge stamina more than once per attack (state set in Combat.attackTargetWith) + if self.turn_procs[t.id] ~= state then + stam_cost = t.getStamina(self, t) + self.turn_procs[t.id] = state + else + stam_cost = 0 + end + --game.log("#GREY#Trained Reactions test for %s: %s %s damage from %s", self.name, dam, type, src.name) + if stam_cost < stam and dam > self.life*t.getLifeTrigger(self, t, stam)/100 then + self:incStamina(-stam_cost) -- Note: force_talent_ignore_ressources has no effect on this + local reduce = t.getReduction(self, t)*(self:attr("never_move") and 0.5 or 1) + if stam_cost > 0 then src:logCombat(self, "#Target# reacts to an attack from #source#, avoiding some damage!") end - cb.value = cb.value * (100-t.getReduction(self, t)) / 100 + dam = dam*(1-reduce) + print("[PROJECTOR] dam after callbackOnTakeDamage", t.id, dam) + return {dam = dam} + end end - return cb.value end, - info = function(self, t) - local trigger = t.getLifeTrigger(self, t) - local reduce = t.getReduction(self, t) - return ([[While this talent is sustained, you anticipate deadly attacks against you. - Any time you would lose more than %d%% of your life in a single hit, you instead duck out of the way and assume a defensive posture. - This reduces the triggering damage and all further damage in the same turn by %d%%. - This costs 10 stamina each time it triggers. - The damage reduction increases based on your Defense.]]) - :format(trigger, reduce, cost) - end, -} - -newTalent{ - name = "Evasion", - type = {"technique/mobility", 4}, - points = 5, - require = techs_dex_req4, - random_ego = "defensive", - tactical = { ESCAPE = 2, DEFEND = 2 }, - cooldown = 30, - stamina = 20, - no_energy = true, - getDur = function(self, t) return math.floor(self:combatTalentLimit(t, 30, 5, 9)) end, -- Limit < 30 - getChanceDef = function(self, t) - if self.perfect_evasion then return 100, 0 end - return self:combatLimit(5*self:getTalentLevel(t) + self:getDex(50,true), 50, 10, 10, 37.5, 75), - self:combatScale(self:getTalentLevel(t) * (self:getDex(50, true)), 0, 0, 55, 250, 0.75) - -- Limit evasion chance < 50%, defense bonus ~= 55 at level 50 + activate = function(self, t) + local ret = {} + ret.life_trigger, ret.life_level = t.getLifeTrigger(self, t) + return ret end, - speed = "combat", - action = function(self, t) - local dur = t.getDur(self,t) - local chance, def = t.getChanceDef(self,t) - self:setEffect(self.EFF_EVASION, dur, {chance=chance, defense = def}) + deactivate = function(self, t, p) return true end, info = function(self, t) - local chance, def = t.getChanceDef(self,t) - return ([[Your quick wit allows you to see melee attacks before they land, granting you a %d%% chance to completely evade them and granting you %d defense for %d turns. - The chance to evade and defense increases with your Dexterity.]]): - format(chance, def,t.getDur(self,t)) + local stam = t.getStamina(self, t) + local triggerMin, triggerFull, triggerCur = t.getLifeTrigger(self, t, stam), t.getLifeTrigger(self, t, self:getMaxStamina()), t.getLifeTrigger(self, t) + local reduce = t.getReduction(self, t, true)*100 + return ([[You have trained to be very light on your feet and have conditioned your reflexes to react faster than thought to attacks as they strike you. + You permanently gain %d%% pinning immunity. + While this talent is active, you reduce the damage of significant melee or archery attacks hitting you by %d%% (improved with Defense). This requires %0.1f stamina per attack. + The nearly instant reactions required are both difficult and exhausting; they cannot be performed while wearing heavy armor and are only half effective if you are immobilized. + Larger attacks are easier to react to and you become more vigilant when wounded, but your reactions slow as your stamina is depleted. The smallest attack your reflexes can affect, as a percent of your current life (currently %d%%), ranges from %d%% at full Stamina to %d%% with minimum Stamina.]]) + :format(t.pinImmune(self, t)*100, reduce, stam, triggerCur, triggerFull, triggerMin) end, -} \ No newline at end of file +} + diff --git a/game/modules/tome/data/talents/techniques/throwing-knives.lua b/game/modules/tome/data/talents/techniques/throwing-knives.lua index 2dcd2b8dc97a6f07330171f03fdec62eb95ba5ef..89d2bf9050f07b24585e826828a1b9cecf94e598 100644 --- a/game/modules/tome/data/talents/techniques/throwing-knives.lua +++ b/game/modules/tome/data/talents/techniques/throwing-knives.lua @@ -218,7 +218,7 @@ newTalent{ range = 0, cooldown = 10, stamina = 30, - radius = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end, + radius = function(self, t) return math.floor(self:combatTalentScale(t, 4, 7)) end, target = function(self, t) return {type="cone", range=0, stop_block = true, friendlyfire=false, radius=t.radius(self, t), display_line_step=false} end, action = function(self, t) local tg = self:getTalentTarget(t) diff --git a/game/modules/tome/data/talents/techniques/unarmed-training.lua b/game/modules/tome/data/talents/techniques/unarmed-training.lua index 243d8c70e2e2f07f0d9a0495545ee43f27314846..31fb9dc9242182150bb63b024392cfe1f5f097ec 100644 --- a/game/modules/tome/data/talents/techniques/unarmed-training.lua +++ b/game/modules/tome/data/talents/techniques/unarmed-training.lua @@ -119,7 +119,7 @@ newTalent{ points = 5, mode = "passive", getFlatReduction = function(self, t) return self:combatTalentScale(t, 30, 70, 0.75) end, - critResist = function(self, t) return self:combatTalentScale(t, 15, 50, 0.75) end, + critResist = function(self, t) return self:combatTalentScale(t, 8, 25, 0.75) end, passives = function(self, t, p) self:talentTemporaryValue(p, "ignore_direct_crits", t.critResist(self, t)) end, diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua index 352f9530c9b2dc725f3e9650f1534884fa6b2ca9..c2ffa10cf40b808a111bebad347b048fc85bfd50 100644 --- a/game/modules/tome/data/timed_effects/magical.lua +++ b/game/modules/tome/data/timed_effects/magical.lua @@ -2364,11 +2364,15 @@ newEffect{ newEffect{ name = "VULNERABILITY_POISON", image = "talents/vulnerability_poison.png", desc = "Vulnerability Poison", - long_desc = function(self, eff) return ("The target is afflicted with a magical poison and is suffering %0.2f arcane damage per turn. All resistances are reduced by 10%% and poison resistance is reduced by 50%%."):format(eff.src:damDesc("ARCANE", eff.power)) end, + long_desc = function(self, eff) + local poison_id = eff.__tmpvals and eff.__tmpvals[2] and eff.__tmpvals[2][2] + local poison_effect = self:getTemporaryValue(poison_id) + return ("The target is afflicted with a magical poison and is suffering %0.2f arcane damage per turn. All resistances are reduced by 10%%%s."):format(eff.src:damDesc("ARCANE", eff.power) , poison_effect and (" and poison resistance is reduced by %s%%"):format(-100*poison_effect) or "") + end, type = "magical", subtype = { poison=true, arcane=true }, status = "detrimental", - parameters = {power=10}, + parameters = {power=10, unresistable=true}, on_gain = function(self, err) return "#Target# is magically poisoned!", "+Vulnerability Poison" end, on_lose = function(self, err) return "#Target# is no longer magically poisoned.", "-Vulnerability Poison" end, -- Damage each turn @@ -2378,14 +2382,12 @@ newEffect{ end end, activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("resists", {all=-10}) + self:effectTemporaryValue(eff, "resists", {all=-10}) if self:attr("poison_immune") and self:checkClassification("living") then - eff.poisonid = self:addTemporaryValue("poison_immune", -self:attr("poison_immune") / 2) + self:effectTemporaryValue(eff, "poison_immune", -self:attr("poison_immune")/2) end end, deactivate = function(self, eff) - self:removeTemporaryValue("resists", eff.tmpid) - if eff.poisonid then self:removeTemporaryValue("poison_immune", eff.poisonid) end end, } diff --git a/game/modules/tome/data/timed_effects/other.lua b/game/modules/tome/data/timed_effects/other.lua index d87f9fede6bb698fc58127f3341f161537eb8b55..77a6ae44e06794ff215b0796f03d3481929356e4 100644 --- a/game/modules/tome/data/timed_effects/other.lua +++ b/game/modules/tome/data/timed_effects/other.lua @@ -3121,8 +3121,8 @@ newEffect{ newEffect{ name = "FUMBLE", image = "talents/fumble.png", desc = "Fumble", - long_desc = function(self, eff) return ("The target is suffering from distracting wounds, giving them a %d%% chance to fail their next talent usage and injure themself."): - format( eff.power*eff.stacks ) end, + long_desc = function(self, eff) return ("The target is suffering from distracting wounds, and has a %d%% chance to fail to use a talent and injure itself for %d physical damage."): + format( eff.power*eff.stacks, eff.dam ) end, charges = function(self, eff) return eff.stacks end, type = "other", subtype = { tactic=true }, diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua index 81c7b9689ace249d57df0c3e704416a71e2ddbd5..3793129422a0a3c854e916e6c14d7417efe5cd1b 100644 --- a/game/modules/tome/data/timed_effects/physical.lua +++ b/game/modules/tome/data/timed_effects/physical.lua @@ -255,7 +255,7 @@ newEffect{ on_gain = function(self, err) return "#Target# is poisoned and cannot move!", "+Spydric Poison" end, on_lose = function(self, err) return "#Target# is no longer poisoned.", "-Spydric Poison" end, activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("never_move", 1) + self:effectTemporaryValue(eff, "never_move", 1) end, -- There are situations this matters, such as copyEffect on_merge = function(self, old_eff, new_eff) @@ -268,7 +268,6 @@ newEffect{ end end, deactivate = function(self, eff) - self:removeTemporaryValue("never_move", eff.tmpid) end, } @@ -283,7 +282,7 @@ newEffect{ on_gain = function(self, err) return "#Target# is poisoned!", "+Insidious Poison" end, on_lose = function(self, err) return "#Target# is no longer poisoned.", "-Insidious Poison" end, activate = function(self, eff) - eff.healid = self:addTemporaryValue("healing_factor", -eff.heal_factor / 100) + self:effectTemporaryValue(eff, "healing_factor", -eff.heal_factor / 100) end, -- There are situations this matters, such as copyEffect on_merge = function(self, old_eff, new_eff) @@ -296,7 +295,6 @@ newEffect{ end end, deactivate = function(self, eff) - self:removeTemporaryValue("healing_factor", eff.healid) end, } @@ -322,10 +320,9 @@ newEffect{ return old_eff end, activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("talent_fail_chance", eff.fail) + self:effectTemporaryValue(eff, "talent_fail_chance", eff.fail) end, deactivate = function(self, eff) - self:removeTemporaryValue("talent_fail_chance", eff.tmpid) end, } @@ -351,10 +348,9 @@ newEffect{ return old_eff end, activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("numbed", eff.reduce) + self:effectTemporaryValue(eff, "numbed", eff.reduce) end, deactivate = function(self, eff) - self:removeTemporaryValue("numbed", eff.tmpid) end, } @@ -402,9 +398,14 @@ newEffect{ old_eff.dur = math.max(old_eff.dur, new_eff.dur) old_eff.power = dam/old_eff.dur if new_eff.max_power then old_eff.power = math.min(old_eff.power, new_eff.max_power) end + old_eff._from_toxic_death = nil return old_eff end, activate = function(self, eff) + if eff._from_toxic_death then -- reset turn counter if spread from Toxic Death + eff.turn_count = 0 + eff._from_toxic_death = nil + end end, deactivate = function(self, eff) -- chance to stone when deactivated local chance = eff.dur <= 0 and eff.turn_count*100/eff.time_to_stone or 0 @@ -1079,10 +1080,11 @@ newEffect{ parameters = { sight=5 }, on_gain = function(self, err) return "#Target# is surrounded by a thick smoke.", "+Dim Vision" end, on_lose = function(self, err) return "The smoke around #target# dissipate.", "-Dim Vision" end, + charges = function(self, eff) return -eff.sight end, activate = function(self, eff) if self.sight - eff.sight < 1 then eff.sight = self.sight - 1 end eff.tmpid = self:addTemporaryValue("sight", -eff.sight) - self:setTarget(nil) -- Loose target! +-- self:setTarget(nil) -- Loose target! self:doFOV() end, deactivate = function(self, eff) @@ -1112,12 +1114,12 @@ newEffect{ newEffect{ name = "WILD_SPEED", image = "talents/infusion__movement.png", desc = "Wild Speed", - long_desc = function(self, eff) return ("The movement infusion allows you to run at extreme fast pace. Any other action other than movement will cancel it. Movement is %d%% faster."):format(eff.power) end, + long_desc = function(self, eff) return ("Moving at extreme speed (%d%% faster). Any action other than movement will cancel it."):format(eff.power) end, type = "physical", subtype = { nature=true, speed=true }, status = "beneficial", parameters = {power=1000}, - on_gain = function(self, err) return "#Target# prepares for the next kill!.", "+Wild Speed" end, + on_gain = function(self, err) return "#Target# is moving at extreme speed!", "+Wild Speed" end, on_lose = function(self, err) return "#Target# slows down.", "-Wild Speed" end, get_fractional_percent = function(self, eff) local d = game.turn - eff.start_turn @@ -2097,7 +2099,7 @@ newEffect{ -- Note: This effect is cancelled by EFF_DISARMED if rng.percent(self:callEffect(eff.effect_id, "deflectchance", adj)) then deflected = eff.dam if self:knowTalent(self.T_TEMPO) then - self:callTalent(self.T_TEMPO, "do_tempo") + self:callTalent(self.T_TEMPO, "do_tempo", src) end end @@ -2888,12 +2890,12 @@ newEffect { -- percent of all damage to ignore reduce = 50 }, - on_gain = function(self, eff) return "#Target# rolls to avoid some damage!" end, + on_gain = function(self, eff) return "#Target# assumes an extreme defensive posture, avoiding some damage!" end, activate = function(self, eff) self:effectTemporaryValue(eff, "incoming_reduce", eff.reduce) end, long_desc = function(self, eff) - return ([[The target is in a defensive roll, ignoring %d%% of all incoming damage.]]) + return ([[The target is in an extreme defensive posture, avoiding %d%% of all incoming damage.]]) :format(eff.reduce) end, } @@ -3091,8 +3093,8 @@ newEffect{ local numbing = eff.numbing > 0 and (" Damage dealt is reduced by %d%%."):format(eff.numbing) or "" local crippling = eff.crippling > 0 and (" %d%% chance to fail talents."):format(eff.crippling) or "" local volatile = eff.volatile > 0 and (" Poison damage also hits adjacent targets."):format() or "" - local leeching = eff.leeching > 0 and (" The source of this effect heals for %d%% of all damage dealt to the target."):format(eff.leeching) or "" - return ("The target is poisoned, doing %0.2f nature damage per turn.%s%s%s%s%s"):format(eff.power, insidious, numbing, crippling, volatile, leeching) + local leeching = eff.leeching > 0 and (" The source of this effect receives healing equal to %d%% of the damage it deals to the target."):format(eff.leeching) or "" + return ("The target is poisoned, taking %0.2f nature damage per turn.%s%s%s%s%s"):format(eff.power, insidious, numbing, crippling, volatile, leeching) end, type = "physical", subtype = { poison=true, nature=true }, no_ct_effect = true, @@ -3105,15 +3107,18 @@ newEffect{ if self:attr("purify_poison") then self:heal(eff.power, eff.src) else + local dam = DamageType:get(DamageType.NATURE).projector(eff.src, self.x, self.y, DamageType.NATURE, eff.power) if eff.volatile > 0 then - local tg = {type="ball", radius=1, friendlyfire=false, x=self.x, y=self.y} + local tg = {type="ball", radius=1, friendlyfire=false, x=self.x, y=self.y, act_exclude={[self.uid]=true}} eff.src:project(tg, self.x, self.y, DamageType.NATURE, eff.power) - else - DamageType:get(DamageType.NATURE).projector(eff.src, self.x, self.y, DamageType.NATURE, eff.power) + end + if dam > 0 and eff.leeching > 0 then + local src = eff.src.resolveSource and eff.src:resolveSource() + if src then src:heal(dam*eff.leeching/100, self) end end end end, - on_merge = function(self, old_eff, new_eff) + on_merge = function(self, old_eff, new_eff) --Note: on_merge called before activate -- Merge the poison local olddam = old_eff.power * old_eff.dur local newdam = new_eff.power * new_eff.dur @@ -3141,21 +3146,16 @@ newEffect{ return old_eff end, activate = function(self, eff) - if eff.insidious > 0 then eff.healid = self:addTemporaryValue("healing_factor", -eff.insidious / 100) end - if eff.numbing > 0 then eff.numbid = self:addTemporaryValue("numbed", eff.numbing) end - if eff.crippling > 0 then eff.cripid = self:addTemporaryValue("talent_fail_chance", eff.crippling) end + -- Only store ids for new temp values (Toxic Death may copy ids from killed victim) + if eff.insidious > 0 then eff.healid = self:addTemporaryValue("healing_factor", -eff.insidious / 100) else eff.healid = nil end + if eff.numbing > 0 then eff.numbid = self:addTemporaryValue("numbed", eff.numbing) else eff.numbid = nil end + if eff.crippling > 0 then eff.cripid = self:addTemporaryValue("talent_fail_chance", eff.crippling) else eff.cripid = nil end end, deactivate = function(self, eff) if eff.healid then self:removeTemporaryValue("healing_factor", eff.healid) end if eff.numbid then self:removeTemporaryValue("numbed", eff.numbid) end if eff.cripid then self:removeTemporaryValue("talent_fail_chance", eff.cripid) end end, - callbackOnHit = function(self, eff, cb, src) - if eff.src == src and eff.leeching > 0 then - src = src.resolveSource and src:resolveSource() - if src then src:heal(cb.value * eff.leeching / 100, self) end - end - end, } newEffect{ @@ -3232,20 +3232,18 @@ newEffect{ newEffect{ name = "SOOTHING_DARKNESS", image = "talents/soothing_darkness.png", desc = "Soothing Darkness", - long_desc = function(self, eff) return ("The target is wreathed in shadows, increasing life regen by %0.1f, stamina regeneration by %0.1f and reducing all damage taken by %d."):format(eff.life, eff.stamina, eff.dr) end, + long_desc = function(self, eff) return ("The target is wreathed in shadows, increasing life regeneration by %0.1f and stamina regeneration by %0.1f."):format(eff.life, eff.stamina) end, type = "physical", subtype = { darkness=true, healing=true }, status = "beneficial", - parameters = { life=10, stamina=5, dr=30 }, + parameters = { life=1, stamina=0.5, dr=0 }, activate = function(self, eff) eff.lifeid = self:addTemporaryValue("life_regen", eff.life) eff.staid = self:addTemporaryValue("stamina_regen", eff.stamina) - eff.drid = self:addTemporaryValue("flat_damage_armor", {all=eff.dr}) end, deactivate = function(self, eff) self:removeTemporaryValue("life_regen", eff.lifeid) self:removeTemporaryValue("stamina_regen", eff.staid) - self:removeTemporaryValue("flat_damage_armor", eff.drid) end, } @@ -3273,15 +3271,16 @@ newEffect{ type = "physical", subtype = { tactical=true, darkness=true }, status = "beneficial", - on_gain = function(self, err) game.logPlayer(self, "#GREY#You begin your shadowdance.") end, - on_lose = function(self, err) game.logPlayer(self, "#GREY#You end your shadowdance.") end, + on_gain = function(self, err) game.logPlayer(self, "#GREY#You begin your Shadow Dance.") end, + on_lose = function(self, err) game.logPlayer(self, "#GREY#You end your Shadow Dance.") end, parameters = {rad=10}, activate = function(self, eff) end, deactivate = function(self, eff) - if not rng.percent(self.hide_chance or 0) then + if not eff.no_cancel_stealth and not rng.percent(self.hide_chance or 0) then local detect = self:stealthDetection(eff.rad) local netstealth = (self:callTalent(self.T_STEALTH, "getStealthPower") + (self:attr("inc_stealth") or 0)) +-- self:alterTalentCoolingdown(self.T_STEALTH, -20) if detect > 0 and self:checkHit(detect, netstealth) then game.logPlayer(self, "You have been detected!") self:forceUseTalent(self.T_STEALTH, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=false}) @@ -3297,22 +3296,30 @@ newEffect{ type = "physical", subtype = { sleep=true, poison=true }, status = "detrimental", - parameters = { power=1, insomnia=1 }, - on_gain = function(self, err) return "#Target# is in a deep sleep.", "+Sedated" end, - on_lose = function(self, err) return "#Target# is no longer sleeping.", "-Sedated" end, + parameters = { power=10, insomnia=10, slow=0 }, + on_gain = function(self, eff) + -- check non-poison immunities if this is being applied by Toxic Death + if eff._from_toxic_death and not (self:checkClassification("living") and self:canBe("sleep")) then + eff.cancel = true + return + end + return "#Target# is in a deep sleep.", "+Sedated" + end, + on_lose = function(self, eff) return "#Target# is no longer sleeping.", "-Sedated" end, on_timeout = function(self, eff) - -- Incriment Insomnia Duration + -- Increment Insomnia Duration if not self:attr("lucid_dreamer") then self:setEffect(self.EFF_INSOMNIA, 1, {power=eff.insomnia}) end end, activate = function(self, eff) - eff.sid = self:addTemporaryValue("sleep", 1) + if eff.cancel then self:removeEffect(eff.effect_id, true) return end + eff._from_toxic_death = false + self:effectTemporaryValue(eff, "sleep", 1) end, deactivate = function(self, eff) - self:removeTemporaryValue("sleep", eff.sid) - if not self:attr("sleep") and not self.dead and game.level:hasEntity(self) and eff.slow > 0 then + if not eff.cancel and not self:attr("sleep") and not self.dead and game.level:hasEntity(self) and eff.slow > 0 then if self:canBe("slow") then self:setEffect(self.EFF_SLOW, 4, {src=eff.src, power=eff.slow, no_ct_effect=true}) end @@ -3510,37 +3517,25 @@ newEffect{ end, } -newEffect{ - name = "AVOIDANCE", image = "talents/disengage.png", - desc = "Avoidance", - long_desc = function(self, eff) return ("%d%% chance to fully evade any damaging actions."):format(eff.power) end, - type = "physical", - subtype = { evade=true }, - status = "beneficial", - parameters = { power=10 }, - activate = function(self, eff) - eff.tmpid = self:addTemporaryValue("cancel_damage_chance", eff.power) - end, - deactivate = function(self, eff) - self:removeTemporaryValue("cancel_damage_chance", eff.tmpid) - end, -} - newEffect{ name = "EXHAUSTION", image = "talents/slumber.png", desc = "Exhaustion", - long_desc = function(self, eff) return ("The target is exhausted, increasing the stamina cost of Tumble by %d."):format(eff.charges*30) end, + long_desc = function(self, eff) return ("The target has recently performed an extreme feat of agility and is exhausted. The stamina cost of activated Mobility talents is increased by %d%%."):format(eff.fatigue) end, type = "other", - subtype = { }, + subtype = {tactic = true}, status = "detrimental", - parameters = { }, - charges = function(self, eff) return eff.charges end, + parameters = {fatigue = 50 }, + charges = function(self, eff) return math.round(eff.fatigue) end, + on_timeout = function(self, eff) + local turns = eff.dur + if turns <= 1 then self:removeEffect(eff.effect_id) return end + eff.fatigue = eff.fatigue*(turns - 1)/turns + end, on_merge = function(self, old_eff, new_eff) - new_eff.charges = math.min(old_eff.charges + 1, 5) + new_eff.fatigue = math.min(old_eff.fatigue + new_eff.fatigue) return new_eff end, activate = function(self, eff) - eff.charges = 1 end, deactivate = function(self, eff) end, @@ -3554,14 +3549,14 @@ newEffect { status = "beneficial", parameters = { -- percent of all damage to ignore - reduce = 50 + reduce = 10 }, - on_gain = function(self, eff) return "#Target# danger sense lets them narrowly avoid the blow!" end, + on_gain = function(self, eff) return ("#RED##Target# instinctively senses danger, protecting %s!"):format(self:his_her_self()) end, activate = function(self, eff) self:effectTemporaryValue(eff, "incoming_reduce", eff.reduce) end, long_desc = function(self, eff) - return ([[The target is avoiding damage, ignoring %d%% of all incoming damage.]]) + return ([[The target is extremely vigilant, avoiding %d%% of all incoming damage.]]) :format(eff.reduce) end, } @@ -3569,7 +3564,10 @@ newEffect { newEffect{ name = "MOBILE_DEFENCE", image = "talents/light_armour_training.png", desc = "Mobile Defense", - long_desc = function(self, eff) return ("Increases stamina regeneration by %d and total defense by %d%%."):format(eff.stamina, eff.power) end, + long_desc = function(self, eff) + local stam = eff.stamina > 0 and ("stamina regeneration by %0.1f and "):format(eff.stamina) or "" + return ("Increases %stotal defense by %d%%."):format(stam, eff.power) + end, type = "physical", subtype = { tactic=true }, status = "beneficial",