Commit 758e5bab8507d25ea5b9681b77ca9bf98c3cca86

Authored by DarkGod
1 parent 9aab931a

New Doomed tree: One with Shadows

... ... @@ -1791,6 +1791,19 @@ function _M:onTakeHit(value, src, death_note)
1791 1791 end
1792 1792 end
1793 1793
  1794 + if value > 0 and self:attr("shadow_empathy") then
  1795 + -- Absorb damage into a random shadow
  1796 + local shadow = self:callTalent(self.T_SHADOW_EMPATHY, "getRandomShadow")
  1797 + if shadow then
  1798 + game:delayedLogMessage(self, src, "displacement_shield"..(shadow.uid or ""), "#CRIMSON##Source# shares some damage with a shadow!")
  1799 + local displaced = math.min(value * self.shadow_empathy / 100, shadow.life)
  1800 + shadow:takeHit(displaced, src)
  1801 + game:delayedLogDamage(src, self, 0, ("#PINK#(%d linked)#LAST#"):format(displaced), false)
  1802 + game:delayedLogDamage(src, shadow, displaced, ("#PINK#%d linked#LAST#"):format(displaced), false)
  1803 + value = value - displaced
  1804 + end
  1805 + end
  1806 +
1794 1807 if value > 0 and self:attr("displacement_shield") then
1795 1808 -- Absorb damage into the displacement shield
1796 1809 if rng.percent(self.displacement_shield_chance) then
... ... @@ -2149,6 +2162,14 @@ function _M:onTakeHit(value, src, death_note)
2149 2162 game.level.map:particleEmitter(self.x, self.y, 1, "teleport_in")
2150 2163 end
2151 2164 end
  2165 +
  2166 + -- Shadow decoy
  2167 + if value >= self.life and self:isTalentActive(self.T_SHADOW_DECOY) then
  2168 + local t = self:getTalentFromId(self.T_SHADOW_DECOY)
  2169 + if t.onDie(self, t, value, src) then
  2170 + value = 0
  2171 + end
  2172 + end
2152 2173
2153 2174 if value <= 0 then return 0 end
2154 2175 -- Vim leech
... ...
... ... @@ -508,6 +508,24 @@ function _M:playerFOV()
508 508 end
509 509 end
510 510
  511 + if self:knowTalent(self.T_SHADOW_SENSE) then
  512 + local t = self:getTalentFromId(self.T_SHADOW_SENSE)
  513 + local range = self:getTalentRange(t)
  514 + local sqsense = range * range
  515 +
  516 + for shadow, _ in pairs(game.party.members) do if shadow.is_doomed_shadow and not shadow.dead then
  517 + local arr = shadow.fov.actors_dist
  518 + local tbl = shadow.fov.actors
  519 + local act
  520 + for i = 1, #arr do
  521 + act = arr[i]
  522 + if act and not act.dead and act.x and tbl[act] and shadow:canSee(act) and tbl[act].sqdist <= sqsense then
  523 + game.level.map.seens(act.x, act.y, 0.6)
  524 + end
  525 + end
  526 + end end
  527 + end
  528 +
511 529 if not self:attr("blind") then
512 530 -- Handle dark vision; same as infravision, but also sees past creeping dark
513 531 -- this is treated as a sense, but is filtered by custom LOS code
... ...
... ... @@ -129,6 +129,7 @@ newBirthDescriptor{
129 129 ["cursed/cursed-form"]={true, 0.0},
130 130 ["cunning/survival"]={false, 0.0},
131 131 ["cursed/fears"]={false, 0.0},
  132 + ["cursed/one-with-shadows"]={false, 0.3},
132 133 },
133 134 talents = {
134 135 [ActorTalents.T_UNNATURAL_BODY] = 1,
... ...
  1 +-- ToME - Tales of Maj'Eyal
  2 +-- Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Casalini
  3 +--
  4 +-- This program is free software: you can redistribute it and/or modify
  5 +-- it under the terms of the GNU General Public License as published by
  6 +-- the Free Software Foundation, either version 3 of the License, or
  7 +-- (at your option) any later version.
  8 +--
  9 +-- This program is distributed in the hope that it will be useful,
  10 +-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +-- GNU General Public License for more details.
  13 +--
  14 +-- You should have received a copy of the GNU General Public License
  15 +-- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16 +--
  17 +-- Nicolas Casalini "DarkGod"
  18 +-- darkgod@te4.org
  19 +
  20 +base_size = 32
  21 +
  22 +return { generator = function()
  23 + local ad = rng.range(0, 360)
  24 + local a = math.rad(ad)
  25 + local dir = math.rad(90)
  26 + local r = rng.range(18, 22)
  27 + local dirchance = rng.chance(2)
  28 + local x = rng.range(-16, 16)
  29 + local y = 16 - math.abs(math.sin(x / 16) * 8)
  30 + local c = rng.float(0, 0.3)
  31 +
  32 + return {
  33 + trail = 1,
  34 + life = rng.range(10, 18),
  35 + size = rng.range(2, 3), sizev = 0, sizea = 0.005,
  36 +
  37 + x = x, xv = 0, xa = 0,
  38 + y = y, yv = 0, ya = -0.2,
  39 + dir = 0, dirv = 0, dira = 0,
  40 + vel = 0, velv = 0, vela = 0,
  41 +
  42 + r = c, rv = 0, ra = 0,
  43 + g = c, gv = 0, ga = 0,
  44 + b = c, bv = 0, ba = 0,
  45 + a = rng.float(0.5, 1), av = 0, aa = 0,
  46 + }
  47 +end, },
  48 +function(self)
  49 + self.ps:emit(4)
  50 +end,
  51 +40
... ...
... ... @@ -31,6 +31,7 @@ newTalentType{ allow_random=true, is_mind=true, type="cursed/force-of-will", nam
31 31 newTalentType{ allow_random=true, is_mind=true, type="cursed/darkness", name = "darkness", description = "Harness the power of darkness to envelop your foes." }
32 32 newTalentType{ allow_random=true, is_mind=true, type="cursed/shadows", name = "shadows", description = "Summon shadows from the darkness to aid you." }
33 33 newTalentType{ allow_random=true, is_mind=true, type="cursed/punishments", name = "punishments", description = "Your hate becomes punishment in the minds of your foes." }
  34 +newTalentType{ allow_random=true, is_mind=true, type="cursed/one-with-shadows", name = "one with shadows", min_lev = 10, description = "Harness your shadows to their full potential." }
34 35
35 36 -- Generic
36 37 newTalentType{ allow_random=true, is_mind=true, type="cursed/gestures", name = "gestures", generic = true, description = "Enhance the power of you mind with gestures." }
... ... @@ -102,6 +103,27 @@ cursed_cun_req5 = {
102 103 level = function(level) return 16 + (level-1) end,
103 104 }
104 105
  106 +cursed_req_high1 = {
  107 + stat = { cun=function(level) return 22 + (level-1) * 2 end },
  108 + level = function(level) return 10 + (level-1) end,
  109 +}
  110 +cursed_req_high2 = {
  111 + stat = { cun=function(level) return 30 + (level-1) * 2 end },
  112 + level = function(level) return 14 + (level-1) end,
  113 +}
  114 +cursed_req_high3 = {
  115 + stat = { cun=function(level) return 38 + (level-1) * 2 end },
  116 + level = function(level) return 18 + (level-1) end,
  117 +}
  118 +cursed_req_high4 = {
  119 + stat = { cun=function(level) return 46 + (level-1) * 2 end },
  120 + level = function(level) return 22 + (level-1) end,
  121 +}
  122 +cursed_req_high5 = {
  123 + stat = { cun=function(level) return 54 + (level-1) * 2 end },
  124 + level = function(level) return 26 + (level-1) end,
  125 +}
  126 +
105 127 cursed_lev_req1 = {
106 128 level = function(level) return 0 + (level-1) end,
107 129 }
... ... @@ -146,6 +168,7 @@ load("/data/talents/cursed/shadows.lua")
146 168 load("/data/talents/cursed/darkness.lua")
147 169 load("/data/talents/cursed/punishments.lua")
148 170 load("/data/talents/cursed/gestures.lua")
  171 +load("/data/talents/cursed/one-with-shadows.lua")
149 172
150 173 load("/data/talents/cursed/cursed-form.lua")
151 174 load("/data/talents/cursed/cursed-aura.lua")
... ...
  1 +-- ToME - Tales of Middle-Earth
  2 +-- Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Casalini
  3 +--
  4 +-- This program is free software: you can redistribute it and/or modify
  5 +-- it under the terms of the GNU General Public License as published by
  6 +-- the Free Software Foundation, either version 3 of the License, or
  7 +-- (at your option) any later version.
  8 +--
  9 +-- This program is distributed in the hope that it will be useful,
  10 +-- but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12 +-- GNU General Public License for more details.
  13 +--
  14 +-- You should have received a copy of the GNU General Public License
  15 +-- along with this program. If not, see <http://www.gnu.org/licenses/>.
  16 +--
  17 +-- Nicolas Casalini "DarkGod"
  18 +-- darkgod@te4.org
  19 +
  20 +local Emote = require "engine.Emote"
  21 +
  22 +newTalent{
  23 + name = "Shadow Senses",
  24 + type = {"cursed/one-with-shadows", 1},
  25 + require = cursed_cun_req_high1,
  26 + mode = "passive",
  27 + points = 5,
  28 + no_npc_use = true,
  29 + range = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, 1)) end,
  30 + info = function(self, t)
  31 + return ([[Your awareness extends to your shadows.
  32 + You always know exactly where your shadows are and can perceive any foe within %d tiles of their vision.]])
  33 + :format(self:getTalentRange(t))
  34 + end,
  35 +}
  36 +
  37 +newTalent{
  38 + name = "Shadow Empathy",
  39 + type = {"cursed/one-with-shadows", 2},
  40 + require = cursed_cun_req_high2,
  41 + points = 5,
  42 + hate = 10,
  43 + cooldown = 25,
  44 + getRandomShadow = function(self, t)
  45 + local shadows = {}
  46 + if game.party and game.party:hasMember(self) then
  47 + for act, def in pairs(game.party.members) do
  48 + if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
  49 + shadows[#shadows+1] = act
  50 + end
  51 + end
  52 + else
  53 + for uid, act in pairs(game.level.entities) do
  54 + if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
  55 + shadows[#shadows+1] = act
  56 + end
  57 + end
  58 + end
  59 + return #shadows > 0 and rng.table(shadows)
  60 + end,
  61 + getDur = function(self, t) return math.floor(self:combatTalentScale(t, 3, 10)) end,
  62 + getPower = function(self, t) return 5 + self:combatTalentMindDamage(t, 0, 300) / 8 end,
  63 + action = function(self, t)
  64 + self:setEffect(self.EFF_SHADOW_EMPATHY, t.getDur(self, t), {power=t.getPower(self, t)})
  65 + return true
  66 + end,
  67 + info = function(self, t)
  68 + local power = t.getPower(self, t)
  69 + local duration = t.getDur(self, t)
  70 + return ([[You are linked to your shadows for %d turns, diverting %d%% of all damage you take to a random shadow.
  71 + Effect increases with Mindpower.]]):
  72 + format(duration, power)
  73 + end,
  74 +}
  75 +
  76 +newTalent{
  77 + name = "Shadow Transposition",
  78 + type = {"cursed/one-with-shadows", 3},
  79 + require = cursed_cun_req_high3,
  80 + points = 5,
  81 + hate = 6,
  82 + no_npc_use = true,
  83 + radius = function(self, t) return math.floor(self:combatTalentScale(t, 1, 15, 1)) end,
  84 + getNb = function(self, t) return math.floor(self:combatTalentScale(t, 1, 3, 1)) end,
  85 + action = function(self, t)
  86 + local tg = {type="hit", range=self:getTalentRadius(t)}
  87 + local x, y, target = self:getTarget(tg)
  88 + if not x or not y or not target then return nil end
  89 + if core.fov.distance(self.x, self.y, target.x, target.y) > self:getTalentRadius(t) then return nil end
  90 + if target.summoner ~= self or not target.is_doomed_shadow then return end
  91 +
  92 + -- Displace
  93 + local tx, ty, sx, sy = target.x, target.y, self.x, self.y
  94 + target.x = nil target.y = nil
  95 + self.x = nil self.y = nil
  96 + target:move(sx, sy, true)
  97 + self:move(tx, ty, true)
  98 +
  99 + self:removeEffectsFilter(function(t) return (t.type == "physical" or t.type == "magical") and t.status == "detrimental" end, t.getNb(self, t))
  100 +
  101 + return true
  102 + end,
  103 + info = function(self, t)
  104 + return ([[Observers find it difficult to tell you and your shadows apart.
  105 + You can target a shadow in radius %d and instantly trade places with it.
  106 + %d random negative physical or magical effects are transferred from you to the chosen shadow in the process.]])
  107 + :format(self:getTalentRadius(t), t.getNb(self, t))
  108 + end,
  109 +}
  110 +
  111 +newTalent{
  112 + name = "Shadow Decoy",
  113 + type = {"cursed/one-with-shadows", 4},
  114 + require = cursed_cun_req_high4,
  115 + mode = "sustained",
  116 + cooldown = 10,
  117 + points = 5,
  118 + cooldown = 50,
  119 + sustain_hate = 40,
  120 + getPower = function(self, t) return 10 + self:combatTalentMindDamage(t, 0, 300) end,
  121 + onDie = function(self, t, value, src)
  122 + local shadow = self:callTalent(self.T_SHADOW_EMPATHY, "getRandomShadow")
  123 + if not shadow then return false end
  124 +
  125 + game:delayedLogDamage(src, self, 0, ("#GOLD#(%d decoy)#LAST#"):format(value), false)
  126 + game:delayedLogDamage(src, shadow, value, ("#GOLD#%d decoy#LAST#"):format(value), false)
  127 + shadow:takeHit(value, src)
  128 + self:setEffect(self.EFF_SHADOW_DECOY, 4, {power=t.getPower(self, t)})
  129 + self:forceUseTalent(t.id, {ignore_energy=true})
  130 +
  131 + if self.player then self:setEmote(Emote.new("Fools, you never killed me; that was only my shadow!", 45)) end
  132 + return true
  133 + end,
  134 + activate = function(self, t)
  135 + return {}
  136 + end,
  137 + deactivate = function(self, t, p)
  138 + return true
  139 + end,
  140 + info = function(self, t)
  141 + return ([[Your shadows guard you with their lifes.
  142 + When you would receive a fatal blow, you instantly transpose with a random shadow that takes the blow instead, putting this talent on cooldown.
  143 + For the next 4 turns you only die if you reach -%d life. However, when below 0 you cannot see how much life you have left.
  144 + Effect increases with Mindpower.]]):
  145 + format(t.getPower(self, t))
  146 + end,
  147 +}
... ...
... ... @@ -194,6 +194,7 @@ local function createShadow(self, level, tCallShadows, tShadowWarriors, tShadowM
194 194 exp_worth=0,
195 195 hate_regen = 1,
196 196 avoid_traps = 1,
  197 + is_doomed_shadow = true,
197 198
198 199 max_life = resolvers.rngavg(3,12), life_rating = 5,
199 200 stats = { -- affected by stat limits
... ...
... ... @@ -258,7 +258,7 @@ newInscription{
258 258 info = function(self, t)
259 259 local data = self:getInscriptionData(t.short_name)
260 260 return ([[Activate the infusion to increase three of your primary stats by %d for %d turns.
261   - Also while Heroism is active, you will only die when reaching -%d life. However, when below 0 you cannot see how much life you have left, and you will die if you did not heal before the effect ends.
  261 + Also while Heroism is active, you will only die when reaching -%d life. However, when below 0 you cannot see how much life you have left.
262 262 It will always increase your three highest stats.]]):format(data.power + data.inc_stat, data.dur, data.die_at + data.inc_stat * 30)
263 263 end,
264 264 short_info = function(self, t)
... ...
... ... @@ -2952,3 +2952,37 @@ newEffect{
2952 2952 eff.power = 2
2953 2953 end,
2954 2954 }
  2955 +
  2956 +newEffect{
  2957 + name = "SHADOW_EMPATHY", image = "talents/shadow_empathy.png",
  2958 + desc = "Shadow Empathy",
  2959 + long_desc = function(self, eff) return ("%d%% of all damage is redirected to a random shadow."):format(eff.power) end,
  2960 + type = "mental",
  2961 + subtype = { mind=true, shield=true },
  2962 + status = "beneficial",
  2963 + parameters = { power=10 },
  2964 + activate = function(self, eff)
  2965 + self:effectTemporaryValue(eff, "shadow_empathy", eff.power)
  2966 + eff.particle = self:addParticles(Particles.new("darkness_power", 1))
  2967 + end,
  2968 + deactivate = function(self, eff)
  2969 + self:removeParticles(eff.particle)
  2970 + end,
  2971 +}
  2972 +
  2973 +newEffect{
  2974 + name = "SHADOW_DECOY", image = "talents/shadow_decoy.png",
  2975 + desc = "Shadow Decoy",
  2976 + long_desc = function(self, eff) return ("A random shadow absorbed a fatal blow for you, granting you a negative shield of %d."):format(eff.power) end,
  2977 + type = "mental",
  2978 + subtype = { mind=true, shield=true },
  2979 + status = "beneficial",
  2980 + parameters = { power=10 },
  2981 + activate = function(self, eff)
  2982 + self:effectTemporaryValue(eff, "die_at", -eff.power)
  2983 + eff.particle = self:addParticles(Particles.new("darkness_power", 1))
  2984 + end,
  2985 + deactivate = function(self, eff)
  2986 + self:removeParticles(eff.particle)
  2987 + end,
  2988 +}
... ...