Code: Select all
-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2009 - 2017 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
-- Compute the total detection ability of enemies to see through stealth
-- Each foe loses 10% detection power per tile beyond range 1
-- returns detect, closest = total detection power, distance to closest enemy
-- if estimate is true, only counts the detection power of seen actors
local function Smog_Devil_stealthDetection(self, radius, estimate)
if not self.x then return nil end
local dist = 0
local closest, detect = math.huge, 0
for i, act in ipairs(self.fov.actors_dist) do
dist = core.fov.distance(self.x, self.y, act.x, act.y)
if dist > radius then break end
if act ~= self and act:reactionToward(self) < 0 and not act:attr("blind") and (not act.fov or not act.fov.actors or act.fov.actors[self]) and (not estimate or self:canSee(act)) then
detect = detect + act:combatSeeStealth() * (1.1 - dist/10) -- detection strength reduced 10% per tile
if dist < closest then closest = dist end
end
end
return detect, closest
end
Talents.Smog_Devil_stealthDetection = Smog_Devil_stealthDetection
-- radius of detection for stealth talents
local function Smog_Devil_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
-- make its stealth scale with spellpower and steampower
newTalent{
short_name = 'smog_devil_stealth',--this needs to get bonuses from steampower and not get as much from cunning
name = "Smog Devil Stealth",
type = {"steamtech/smog-and-steam-veil", 1},
require = cuns_req1,
drain_steam = 0.5,
mode = "sustained", no_sustain_autoreset = true,
points = 5,
cooldown = 10,
allow_autocast = true,
no_energy = true,
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, 0.25)) end, --TL 5, cun 100 = 64
getRadius = Smog_Devil_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
if not silent then game.logPlayer(self, "You cannot be stealthy with such heavy armour on!") end
return nil
end
if self:isTalentActive(t.id) then return true end
-- Check nearby actors detection ability
if not self.x or not self.y or not game.level then return end
if not rng.percent(self.hide_chance or 0) then
if Smog_Devil_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
return nil
end
end
return true
end,
sustain_lists = "break_with_stealth",
activate = function(self, t)
if self:knowTalent(self.T_SOOTHING_DARKNESS) then
local life = self:callTalent(self.T_SOOTHING_DARKNESS, "getLife")
local dur = self:callTalent(self.T_SOOTHING_DARKNESS, "getDuration")
self:setEffect(self.EFF_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM", dur, {life=life})
end
local res = {
stealth = self:addTemporaryValue("stealth", t.getStealthPower(self, t)),
lite = self:addTemporaryValue("lite", -1000),
infra = self:addTemporaryValue("infravision", 1),
}
self:resetCanSeeCacheOf()
if self.updateMainShader then self:updateMainShader() end
return res
end,
deactivate = function(self, t, p)
self:removeTemporaryValue("stealth", p.stealth)
self:removeTemporaryValue("infravision", p.infra)
self:removeTemporaryValue("lite", p.lite)
if self:knowTalent(self.T_TERRORIZE) then
local t = self:getTalentFromId(self.T_TERRORIZE)
t.terrorize(self,t)
end
if self:knowTalent(self.T_SMOG_DEVIL_SHADOWSTRIKE) then
local power = self:callTalent(self.T_SMOG_DEVIL_SHADOWSTRIKE, "getMultiplier") * 100
local dur = self:callTalent(self.T_SMOG_DEVIL_SHADOWSTRIKE, "getDuration")
self:setEffect(self.EFF_SHADOWSTRIKE, dur, {power=power})
end
if self:knowTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM) then
local life = self:callTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, "getLife") * 5
local dur = self:callTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, "getDuration")
self:setEffect(self.EFF_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, dur, {life=life})
end
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
return true
end,
callbackOnActBase = function(self, t)
if self:knowTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM) then
local life = self:callTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, "getLife")
local dur = self:callTalent(self.T_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, "getDuration")
self:setEffect(self.EFF_SMOG_DEVIL_CLOAK_OF_SHADOWS_AND_STEAM, dur, {life=life})
end
end,
info = function(self, t)
local stealthpower = t.getStealthPower(self, t) + (self:attr("inc_stealth") or 0)
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 and steampower), 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%s.
Any non-instant, non-movement action will break stealth if not otherwise specified.]]):
format(stealthpower, radius, xs)
end,
}
---smog devils shadow strike is a version that has less mult but slightly longer duration of 2nd effect
newTalent{
short_name = 'smog_devil_shadowstrike',
name = "Smog Devil's Shadowstrike",
type = {"steamtech/smog-and-steam-veil", 2},
require = cuns_req2,--change to scaling requirements based on Smog Devil class
mode = "passive",
points = 5,
getMultiplier = function(self, t) return self:combatTalentScale(t, 0.15, 0.40, 0.1) end, --scale this slightly lower
getDuration = function(self,t) if self:getTalentLevel(t) >= 4 then return 5 else return 4 end end,
passives = function(self, t, p) -- attribute that increases crit multiplier vs targets that cannot see us
self:talentTemporaryValue(p, "unseen_critical_power", t.getMultiplier(self, t))
end,
info = function(self, t)
local multiplier = t.getMultiplier(self, t)*100
local dur = t.getDuration(self, t)
return ([[You know how to make the most out of being unseen.
When striking from stealth, your attacks are automatically critical if the target does not notice you just before you land it. (Spell and mind attacks critically strike even if the target notices you.)
Your critical multiplier against targets that cannot see you is increased by up to %d%%. (You must be able to see your target and the bonus is reduced from its full value at range 5 to 0 at range 10.)
Also, after exiting stealth for any reason, the critical multiplier persists for %d turns (with no range limitation).]]):format(multiplier, dur)
end,
}
newTalent{
short_name = 'smog_devil_cloak_of_shadows_and_steam', --give a other effect that stacks to while in steam and smog from Smog Devil's attacks (not from explosions though) that will decrease a stack while outside of these map effects/attacks, while you have at least 1 stack of fog veil(or whatever i name the stack effect) the steam the drained steam from the stealth will be refunded in the form of steam regen, gains a bonus to stealth based on how many stacks of fog veil the user has (scales with steampower)
name = "Cloak of Shadows and Steam",--steam / gas based replacement for soothing Darkness
type = {"steamtech/smog-and-steam-veil", 3},
require = cuns_req3,
points = 5,
mode = "passive",
getLife = function(self, t) return self:combatStatScale("cun", 0.5, 5, 0.75) + self:combatTalentScale(t, 0.5, 5, 0.75) end,
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 4 else return 3 end end,
info = function(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, your life regeneration is increased by %0.1f (based on your Cunning) and your stamina regeneration is increased by %0.1f. The regeneration effects persist for %d turns after exiting stealth, with 5 times the normal life regeneration rate.]]):
format(t.getRadius(self, t, true), t.getLife(self,t), t.getBonusSteamregen(self,t), t.getDuration(self, t))
end,
}
newTalent{
short_name = 'smog_devil_dance_of_shadows_and_steam',
name = "Dance of Shadows and Steam",--this is pretty much just renamed shadow dance with skills it uses being different and steam cost instead of stamina cost (will also be able to reduce range requirement of unlit grid based on if user has full stack of fog veil)
type = {"steamtech/smog-and-steam-veil", 4},
require = cuns_req4,
no_energy = true,
no_break_stealth = true,
points = 5,
steam = 30,
cooldown = function(self, t) return self:combatTalentLimit(t, 10, 30, 15) end,
tactical = { DEFEND = 2, ESCAPE = 2 },
getRadius = Smog_Devil_stealthRadius,
getDuration = function(self, t) return math.floor(self:combatTalentLimit(t, 7, 2, 5)) end,
action = function(self, t)
if not self:isTalentActive(self.T_SMOG_DEVIL_STEALTH) then
self:forceUseTalent(self.T_SMOG_DEVIL_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_SMOG_DEVIL_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)
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,
}