Showing
13 changed files
with
297 additions
and
1 deletions
... | ... | @@ -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 | ... | ... |

3.39 KB

3.54 KB

5.09 KB

4 KB
... | ... | @@ -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 | +} | ... | ... |
-
Please register or login to post a comment