Commit 888c7f7f2f66ad92b81dea527f3fd5f44a18aede

Authored by DarkGod
1 parent 787cef6a

new necro WIP

... ... @@ -3261,15 +3261,6 @@ function _M:die(src, death_note)
3261 3261 src.blood_frenzy = src.blood_frenzy + src:callTalent(src.T_BLOOD_FRENZY,"bonuspower")
3262 3262 end
3263 3263
3264   - -- Increases necrotic aura count
3265   - if src and src.resolveSource and src:resolveSource().isTalentActive and src:resolveSource():isTalentActive(src.T_NECROTIC_AURA) and not self.necrotic_minion and not self.no_necrotic_soul then
3266   - local rsrc = src:resolveSource()
3267   - local p = rsrc:isTalentActive(src.T_NECROTIC_AURA)
3268   - if self.x and self.y and src.x and src.y and core.fov.distance(self.x, self.y, rsrc.x, rsrc.y) <= rsrc.necrotic_aura_radius then
3269   - rsrc:callTalent(rsrc.T_NECROTIC_AURA, "absorbSoul", self)
3270   - end
3271   - end
3272   -
3273 3264 -- handle hate changes on kill
3274 3265 if src and src.knowTalent and src:knowTalent(src.T_HATE_POOL) then
3275 3266 local t = src:getTalentFromId(src.T_HATE_POOL)
... ...
... ... @@ -229,6 +229,7 @@ function _M:newGame()
229 229 self.zone.object_list[#self.zone.object_list+1] = o
230 230 end
231 231
  232 + self.player.innate_player = true
232 233 if config.settings.cheat then self.player.__cheated = true end
233 234
234 235 if game.__mod_info then
... ...
... ... @@ -1697,9 +1697,8 @@ function _M:combatDamagePower(weapon_combat, add)
1697 1697 return (math.sqrt(power / 10) - 1) * 0.5 + 1
1698 1698 end
1699 1699
1700   -function _M:combatPhysicalpower(mod, weapon, add)
  1700 +function _M:combatPhysicalpowerRaw(weapon, add)
1701 1701 if self.combat_precomputed_physpower then return self.combat_precomputed_physpower end
1702   - mod = mod or 1
1703 1702 add = add or 0
1704 1703
1705 1704 if self.combat_generic_power then
... ... @@ -1737,6 +1736,13 @@ function _M:combatPhysicalpower(mod, weapon, add)
1737 1736
1738 1737 if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end
1739 1738
  1739 + return d
  1740 +end
  1741 +
  1742 +function _M:combatPhysicalpower(mod, weapon, add)
  1743 + mod = mod or 1
  1744 + local d = self:combatPhysicalpowerRaw(weapon, add)
  1745 +
1740 1746 if self:knowTalent(self.T_ARCANE_MIGHT) then
1741 1747 return self:combatSpellpower(mod, d) -- will do the rescaling and multiplying for us
1742 1748 else
... ... @@ -1752,10 +1758,9 @@ function _M:combatTalentPhysicalDamage(t, base, max)
1752 1758 return self:rescaleDamage((base + (self:combatPhysicalpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
1753 1759 end
1754 1760
1755   ---- Gets spellpower
1756   -function _M:combatSpellpower(mod, add)
  1761 +--- Gets spellpower raw
  1762 +function _M:combatSpellpowerRaw(add)
1757 1763 if self.combat_precomputed_spellpower then return self.combat_precomputed_spellpower end
1758   - mod = mod or 1
1759 1764 add = add or 0
1760 1765
1761 1766 if self.combat_generic_power then
... ... @@ -1783,6 +1788,13 @@ function _M:combatSpellpower(mod, add)
1783 1788
1784 1789 if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end
1785 1790
  1791 + return d, am
  1792 +end
  1793 +
  1794 +--- Gets spellpower
  1795 +function _M:combatSpellpower(mod, add)
  1796 + mod = mod or 1
  1797 + local d, am = self:combatSpellpowerRaw(add)
1786 1798 return self:rescaleCombatStats(d) * mod * am
1787 1799 end
1788 1800
... ... @@ -2069,10 +2081,9 @@ function _M:spellFriendlyFire()
2069 2081 end
2070 2082
2071 2083 --- Gets mindpower
2072   -function _M:combatMindpower(mod, add)
  2084 +function _M:combatMindpowerRaw(add)
2073 2085 if self.combat_precomputed_mindpower then return self.combat_precomputed_mindpower end
2074 2086
2075   - mod = mod or 1
2076 2087 add = add or 0
2077 2088
2078 2089 if self.combat_generic_power then
... ... @@ -2104,6 +2115,13 @@ function _M:combatMindpower(mod, add)
2104 2115
2105 2116 if self:attr("hit_penalty_2h") then d = d * (1 - math.max(0, 20 - (self.size_category - 4) * 5) / 100) end
2106 2117
  2118 + return d
  2119 +end
  2120 +
  2121 +--- Gets mindpower
  2122 +function _M:combatMindpower(mod, add)
  2123 + mod = mod or 1
  2124 + local d = self:combatMindpowerRaw(add)
2107 2125 return self:rescaleCombatStats(d) * mod
2108 2126 end
2109 2127
... ... @@ -2147,9 +2165,9 @@ function _M:combatStatTalentIntervalDamage(t, stat, min, max, stat_weight)
2147 2165 return self:rescaleDamage(min + (max - min)*((stat_weight * self[stat](self)/100) + (1 - stat_weight) * self:getTalentLevel(t)/6.5))
2148 2166 end
2149 2167
2150   ---- Computes physical resistance
  2168 +--- Computes physical resistance (before scaling)
2151 2169 --- Fake denotes a check not actually being made, used by character sheets etc.
2152   -function _M:combatPhysicalResist(fake, add)
  2170 +function _M:combatPhysicalResistRaw(fake, add)
2153 2171 add = add or 0
2154 2172 if not fake then
2155 2173 add = add + (self:checkOnDefenseCall("physical") or 0)
... ... @@ -2165,8 +2183,13 @@ function _M:combatPhysicalResist(fake, add)
2165 2183 local d = self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add
2166 2184 if self:attr("dazed") then d = d / 2 end
2167 2185
  2186 + return d
  2187 +end
2168 2188
2169   - local total = self:rescaleCombatStats(d)
  2189 +--- Computes physical resistance (before scaling)
  2190 +--- Fake denotes a check not actually being made, used by character sheets etc.
  2191 +function _M:combatPhysicalResist(fake, add)
  2192 + local total = self:rescaleCombatStats(self:combatPhysicalResistRaw(fake, add))
2170 2193
2171 2194 -- Psionic Balance
2172 2195 if self:knowTalent(self.T_BALANCE) then
... ... @@ -2177,9 +2200,9 @@ function _M:combatPhysicalResist(fake, add)
2177 2200 return total
2178 2201 end
2179 2202
2180   ---- Computes spell resistance
  2203 +--- Computes spell resistance raw
2181 2204 --- Fake denotes a check not actually being made, used by character sheets etc.
2182   -function _M:combatSpellResist(fake, add)
  2205 +function _M:combatSpellResistRaw(fake, add)
2183 2206 add = add or 0
2184 2207 if not fake then
2185 2208 add = add + (self:checkOnDefenseCall("spell") or 0)
... ... @@ -2194,7 +2217,14 @@ function _M:combatSpellResist(fake, add)
2194 2217 -- To return later
2195 2218 local d = self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
2196 2219 if self:attr("dazed") then d = d / 2 end
2197   - local total = self:rescaleCombatStats(d)
  2220 +
  2221 + return d
  2222 +end
  2223 +
  2224 +--- Computes spell resistance
  2225 +--- Fake denotes a check not actually being made, used by character sheets etc.
  2226 +function _M:combatSpellResist(fake, add)
  2227 + local total = self:rescaleCombatStats(self:combatSpellResistRaw(fake, add))
2198 2228
2199 2229 -- Psionic Balance
2200 2230 if self:knowTalent(self.T_BALANCE) then
... ... @@ -2205,9 +2235,9 @@ function _M:combatSpellResist(fake, add)
2205 2235 return total
2206 2236 end
2207 2237
2208   ---- Computes mental resistance
  2238 +--- Computes mental resistance raw
2209 2239 --- Fake denotes a check not actually being made, used by character sheets etc.
2210   -function _M:combatMentalResist(fake, add)
  2240 +function _M:combatMentalResistRaw(fake, add)
2211 2241 add = add or 0
2212 2242 if not fake then
2213 2243 add = add + (self:checkOnDefenseCall("mental") or 0)
... ... @@ -2230,7 +2260,14 @@ function _M:combatMentalResist(fake, add)
2230 2260 if nm and rng.percent(20) and not fake then
2231 2261 d = d * (1-self.tempeffect_def.EFF_CURSE_OF_NIGHTMARES.getVisionsReduction(nm, nm.level)/100)
2232 2262 end
2233   - return self:rescaleCombatStats(d)
  2263 +
  2264 + return d
  2265 +end
  2266 +
  2267 +--- Computes mental resistance
  2268 +--- Fake denotes a check not actually being made, used by character sheets etc.
  2269 +function _M:combatMentalResist(fake, add)
  2270 + return self:rescaleCombatStats(self:combatMentalResistRaw(fake, add))
2234 2271 end
2235 2272
2236 2273 -- Called when a Save or Defense is checked
... ... @@ -2301,7 +2338,7 @@ end
2301 2338 --- Returns the damage increase
2302 2339 function _M:combatGetDamageIncrease(type, straight)
2303 2340 local a = self.inc_damage.all or 0
2304   - local b = self.inc_damage[type] or 0
  2341 + local b = type ~= "all" and self.inc_damage[type] or 0
2305 2342 local inc = a + b
2306 2343 if straight then return inc end
2307 2344
... ...
  1 +-- ToME - Tales of Maj'Eyal
  2 +-- Copyright (C) 2009 - 2019 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 +--------------------------------------------------------------------------------------
  21 +-- Advanced shaders
  22 +--------------------------------------------------------------------------------------
  23 +if core.shader.active(4) then
  24 +local life = 20
  25 +use_shader = {type="shockwave2", shockwaveSpeed=7*16/life}
  26 +base_size = 64
  27 +
  28 +local nb = 0
  29 +local radius = radius or 6
  30 +
  31 +return {
  32 + generator = function()
  33 + return {
  34 + life = life,
  35 + size = 2.1*64*radius, sizev = 0, sizea = 0,
  36 +
  37 + x = 0, xv = 0, xa = 0,
  38 + y = 0, yv = 0, ya = 0,
  39 + dir = 0, dirv = dirv, dira = 0,
  40 + vel = 0, velv = 0, vela = 0,
  41 +
  42 + r = 1, rv = 0, ra = 0,
  43 + g = 1, gv = 0, ga = 0,
  44 + b = 1, bv = 0, ba = 0,
  45 + a = 0.8, av = -0.01, aa = 0,
  46 + }
  47 +end, },
  48 +function(self)
  49 + if nb < 1 then
  50 + self.ps:emit(1)
  51 + end
  52 + nb = nb + 1
  53 +end,
  54 +1, "particles_images/darkwings"
  55 +
  56 +
  57 +--------------------------------------------------------------------------------------
  58 +-- Default
  59 +--------------------------------------------------------------------------------------
  60 +else
  61 +local nb = 12
  62 +local dir
  63 +local radius = radius or 6
  64 +
  65 +return { generator = function()
  66 + local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2
  67 + local ad = rng.float(0, 360)
  68 + local a = math.rad(ad)
  69 + local r = 0
  70 + local x = r * math.cos(a)
  71 + local y = r * math.sin(a)
  72 + local static = rng.percent(40)
  73 + local vel = sradius * ((24 - nb * 1.4) / 24) / 12
  74 +
  75 + return {
  76 + trail = 1,
  77 + life = 12,
  78 + size = 12 - (12 - nb) * 0.7, sizev = 0, sizea = 0,
  79 +
  80 + x = x, xv = 0, xa = 0,
  81 + y = y, yv = 0, ya = 0,
  82 + dir = a, dirv = 0, dira = 0,
  83 + vel = rng.float(vel * 0.6, vel * 1.2), velv = 0, vela = 0,
  84 +
  85 + r = rng.range(0, 25)/255, rv = 0, ra = 0,
  86 + g = rng.range(0, 25)/255, gv = 0, ga = 0,
  87 + b = rng.range(0, 25)/255, bv = 0, ba = 0,
  88 + a = rng.range(0, 25)/255, av = 0, aa = 0.005,
  89 + }
  90 +end, },
  91 +function(self)
  92 + if nb > 0 then
  93 + local i = math.min(nb, 6)
  94 + i = (i * i) * radius
  95 + self.ps:emit(i)
  96 + nb = nb - 1
  97 + end
  98 +end,
  99 +30*radius*7*12,
  100 +"particle_cloud"
  101 +
  102 +end
\ No newline at end of file
... ...
... ... @@ -71,6 +71,11 @@ Talents.newTalent = function(self, t)
71 71 t.name = ("#SANDY_BROWN# (Race Evolution)"):tformat(_t(t.name))
72 72 end
73 73
  74 + -- Generate easier, reverse parameters, calls for methods
  75 + for k, e in pairsclone(t) do if type(e) == "function" and type(k) == "string" then
  76 + t["_"..k] = function(t, self, ...) return e(self, t, ...) end
  77 + end end
  78 +
74 79 return oldNewTalent(self, t)
75 80 end
76 81
... ...
... ... @@ -27,7 +27,8 @@ newTalent{
27 27 getTurns = function(self, t) return self:combatTalentLimit(t, 1, 10, 3) end,
28 28 getTurnsByRank = function(self, t, target)
29 29 local base = t.getTurns(self, t)
30   - if target.rank == 3.2 then return math.ceil(base * 3), true
  30 + if target.innate_player then return math.ceil(base), true -- Player is like a boss for NPCs
  31 + elseif target.rank == 3.2 then return math.ceil(base * 3), true
31 32 elseif target.rank == 3.5 then return math.ceil(base * 2.2), true
32 33 elseif target.rank == 4 then return math.ceil(base * 1.3), true
33 34 elseif target.rank == 5 then return math.ceil(base), true
... ... @@ -37,7 +38,7 @@ newTalent{
37 38 end,
38 39 callbackOnDealDamage = function(self, t, val, target, dead, death_note)
39 40 if dead then
40   - self:incSoul(1)
  41 + self:resolveSource():incSoul(1)
41 42 else
42 43 if target:hasEffect(target.EFF_SOUL_LEECH) then return end -- Dont reset, we want it to exprei to leech
43 44 local turns, powerful = t.getTurnsByRank(self, t, target)
... ... @@ -51,7 +52,7 @@ newTalent{
51 52 npc.rank = 4 local _, c_boss = npc:TextRank()
52 53 npc.rank = 5 local _, c_eboss = npc:TextRank()
53 54
54   - return ([[Each time you deal damage to a creature you apply Soul Leech to them.
  55 + return ([[Each time you or your undead minions deal damage to a creature you apply Soul Leech to them.
55 56 If a creature dies with this effect active, you steal its soul.
56 57 Strong creatures and bosses are so overflowing with soul power that you steal a fragment of their soul every few turns:
57 58 %s- rare: at most every %d turns
... ... @@ -130,6 +131,7 @@ newTalent{
130 131 require = spells_req4,
131 132 points = 5,
132 133 mode = "sustained",
  134 + cooldown = 30,
133 135 sustain_mana = 30,
134 136 getNb = function(self, t) return math.floor(self:combatTalentScale(t, 2, 8)) end,
135 137 getMana = function(self, t) return math.floor(self:combatTalentScale(t, 5, 30)) / 10 end,
... ...
... ... @@ -33,11 +33,7 @@ newTalent{
33 33 getDamage = function(self, t) return self:combatTalentSpellDamage(t, 30, 300) end,
34 34 getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
35 35 target = function(self, t)
36   - if necroEssenceDead(self, true) then
37   - return {type="ball", radius=2, range=self:getTalentRange(t), talent=t}
38   - else
39   - return {type="hit", range=self:getTalentRange(t), talent=t}
40   - end
  36 + return {type="hit", range=self:getTalentRange(t), talent=t}
41 37 end,
42 38 action = function(self, t)
43 39 local tg = self:getTalentTarget(t)
... ... @@ -45,12 +41,6 @@ newTalent{
45 41 if not x or not y then return nil end
46 42 if not x or not y then return nil end
47 43
48   - if necroEssenceDead(self, true) then
49   - self:projectApply(tg, x, y, Map.ACTOR, function(target) target:setEffect(target.EFF_WET, t.getDuration(self, t), {apply_power=self:combatSpellpower()}) end)
50   - local empower = necroEssenceDead(self)
51   - if empower then empower() end
52   - end
53   -
54 44 local dam = self:spellCrit(t.getDamage(self, t))
55 45 self:project(tg, x, y, DamageType.COLD, dam, {type="freeze"})
56 46 self:project(tg, x, y, DamageType.FREEZE, {dur=t.getDuration(self, t), hp=70 + dam * 0.7})
... ... @@ -68,8 +58,8 @@ newTalent{
68 58 info = function(self, t)
69 59 local damage = t.getDamage(self, t)
70 60 return ([[Condenses ambient water on a target, freezing it for %d turns and damaging it for %0.2f.
71   - If this is used on a friendly target the cooldown is reduced by 33%%.%s
72   - The damage will increase with your Spellpower.]]):tformat(t.getDuration(self, t), damDesc(self, DamageType.COLD, damage), necroEssenceDead(self, true) and _t"\nAffects all creatures in radius 2." or "")
  61 + If this is used on a friendly target the cooldown is reduced by 33%%.
  62 + The damage will increase with your Spellpower.]]):tformat(t.getDuration(self, t), damDesc(self, DamageType.COLD, damage))
73 63 end,
74 64 }
75 65
... ...
  1 +-- ToME - Tales of Maj'Eyal
  2 +-- Copyright (C) 2009 - 2020 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 +newTalent{
  21 + name = "Aura of Undeath", short_name = "NECROTIC_AURA", image = "talents/aura_mastery.png",
  22 + type = {"spell/master-necromancer",1},
  23 + require = spells_req_high1,
  24 + points = 5,
  25 + mode = "sustained",
  26 + sustain_mana = 15,
  27 + cooldown = 20,
  28 + tactical = { BUFF = 3 },
  29 + radius = function(self, t) return math.floor(util.bound(3 + self:getTalentLevel(t), 4, 10)) end,
  30 + getResists = function(self, t) return math.floor(self:combatTalentScale(t, 8, 18)) end,
  31 + getInherit = function(self, t) return math.floor(self:combatTalentLimit(t, 75, 20, 40)) end,
  32 + callbackOnActBase = function(self, t)
  33 + self:projectApply({type="ball", radius=self:getTalentRadius(t)}, self.x, self.y, Map.ACTOR, function(target)
  34 + if target.summoner == self and target.necrotic_minion and not target:hasEffect(target.EFF_NECROTIC_AURA) then
  35 + target:setEffect(target.EFF_NECROTIC_AURA, 1, {power=t:_getResists(self)})
  36 + end
  37 + end, "friend")
  38 + end,
  39 + activate = function(self, t)
  40 + local ret = {}
  41 + self:talentParticles(ret, {type="necrotic-aura", args={radius=self:getTalentRadius(t)}})
  42 + self:talentParticles(ret, {type="circle", args={oversize=0.7, a=75, appear=8, speed=8, img="necro_aura", radius=self:getTalentRadius(t)}})
  43 + game:playSoundNear(self, "talents/spell_generic2")
  44 + return ret
  45 + end,
  46 + deactivate = function(self, t)
  47 + return true
  48 + end,
  49 + info = function(self, t)
  50 + return ([[Your mastery of necromancy becomes so total that an aura of undeath radiates around you in radius %d.
  51 + Any undead minion standing inside of it is protected, increasing all their resistances by %d%%.
  52 + In addition when you create new minions they inherit %d%% of your spellpower (applied to any powers), spell crit chance (applied to any crit chances), saves, resists and damage increases (applied to all elements).
  53 + ]]):tformat(self:getTalentRadius(t), t:_getResists(self), t:_getInherit(self))
  54 + end,
  55 +}
  56 +
  57 +newTalent{
  58 + name = "Surge of Undeath",
  59 + type = {"spell/master-necromancer", 2},
  60 + require = spells_req_high2,
  61 + points = 5,
  62 + mana = 30,
  63 + cooldown = 18,
  64 + tactical = { BUFF=function(self) return necroArmyStats(self).nb / 2 end, DISABLE = {daze=2} },
  65 + range = 0,
  66 + radius = function(self, t) return self:callTalent(self.T_NECROTIC_AURA, "radius") end,
  67 + target = function(self, t) return {type="ball", range=0, radius=self:getTalentRadius(t)} end,
  68 + requires_target = true,
  69 + getSpeed = function(self, t) return math.floor(self:combatTalentScale(t, 2, 5)) end,
  70 + getDaze = function(self, t) return math.floor(self:combatTalentScale(t, 4, 10)) end,
  71 + on_pre_use = function(self, t) return self:isTalentActive(self.T_NECROTIC_AURA) end,
  72 + action = function(self, t, p)
  73 + local tg = self:getTalentTarget(t)
  74 + self:projectApply(tg, self.x, self.y, Map.ACTOR, function(target)
  75 + if self:reactionToward(target) < 0 and not target:attr("undead") then
  76 + if target:canBe("stun") then target:setEffect(target.EFF_DAZED, t:_getDaze(self), {apply_power=self:combatSpellpower()}) end
  77 + elseif target.summoner == self and target.necrotic_minion then
  78 + target:setEffect(target.EFF_HASTE, t:_getSpeed(self), {power=0.25})
  79 + end
  80 + end)
  81 + game.level.map:particleEmitter(self.x, self.y, tg.radius, "ball_darkness", {radius=tg.radius})
  82 + return true
  83 + end,
  84 + info = function(self, t)
  85 + return ([[Sends out a surge of undeath energies into your aura.
  86 + All minions inside gain 25%% speed for %d turns.
  87 + All non-undead foes caught inside are dazed for %d turns.]]):
  88 + tformat(t:_getSpeed(self), t:_getDaze(self))
  89 + end,
  90 +}
  91 +
  92 +newTalent{
  93 + name = "Recall Minions",
  94 + type = {"spell/master-necromancer", 3},
  95 + require = spells_req_high3,
  96 + points = 5,
  97 + mana = 25,
  98 + soul = 1,
  99 + cooldown = 20,
  100 + tactical = { ESCAPE=3 },
  101 + range = 0,
  102 + radius = function(self, t) return self:callTalent(self.T_NECROTIC_AURA, "radius") end,
  103 + target = function(self, t) return {type="ball", range=0, radius=self:getTalentRadius(t)} end,
  104 + requires_target = true,
  105 + getNb = function(self, t) return math.floor(self:combatTalentLimit(t, 8, 1, 6)) end,
  106 + on_pre_use = function(self, t) return self:isTalentActive(self.T_NECROTIC_AURA) and necroArmyStats(self).nb > 0 end,
  107 + action = function(self, t)
  108 + local stats = necroArmyStats(self)
  109 + if stats.nb == 0 then return end
  110 +
  111 + local spots, spots_hostile = {}, {}
  112 + self:projectApply({type="ball", radius=1}, self.x, self.y, Map.TERRAIN, function(_, x, y)
  113 + local target = game.level.map(x, y, Map.ACTOR)
  114 + if target and self:reactionToward(target) < 0 then spots_hostile[#spots_hostile+1] = {x=x, y=y, foe=target}
  115 + elseif not target then spots[#spots+1] = {x=x, y=y}
  116 + end
  117 + end)
  118 +
  119 + for i = 1, t:_getNb(self) do
  120 + local m = rng.tableRemove(stats.list)
  121 + if not m then break end
  122 + local spot = rng.tableRemove(spots_hostile)
  123 + if not spot then spot = rng.tableRemove(spots) end
  124 + if not spot then break end
  125 +
  126 + local mx, my = m.x, m.y
  127 + m:forceMoveAnim(spot.x, spot.y)
  128 + if spot.foe then spot.foe:forceMoveAnim(mx, my) end
  129 + end
  130 +
  131 + return true
  132 + end,
  133 + info = function(self, t)
  134 + return ([[Tigthen the ethereal leash to some of your minions currently within your aura of undeath, pulling them to you and swapping place with any eventual foes in the way.
  135 + Up to %d minions are affected.
  136 + When recalling a minion the spell tries to prioritize a spot where there is already a foe, to push it away.]]):
  137 + tformat(t:_getNb(self, t))
  138 + end,
  139 +}
  140 +
  141 +newTalent{
  142 + name = "Suffer For Me",
  143 + type = {"spell/master-necromancer",4},
  144 + require = spells_req_high4,
  145 + points = 5,
  146 + mode = "sustained",
  147 + sustain_mana = 30,
  148 + cooldown = 30,
  149 + getPower = function(self, t) return util.bound(self:combatTalentSpellDamage(t, 20, 330) / 10, 5, 40) end,
  150 + callbackOnHit = function(self, t, cb, src)
  151 + if not self:isTalentActive(self.T_NECROTIC_AURA) then return end
  152 + if not cb.value then return end
  153 + local stats = necroArmyStats(self)
  154 + if stats.nb == 0 then return end
  155 +
  156 + while true do
  157 + local m = rng.tableRemove(stats.list)
  158 + if not m then return end
  159 +
  160 + if m:hasEffect(m.EFF_NECROTIC_AURA) then
  161 + local remain = cb.value * t:_getPower(self) / 100
  162 + cb.value = cb.value - remain
  163 + game:delayedLogDamage(src, self, 0, ("#GREY#(%d to minion: %s)#LAST#"):tformat(remain, m:getName()), false)
  164 + m:takeHit(remain * 3, src or self)
  165 + return true
  166 + end
  167 + end
  168 + end,
  169 + activate = function(self, t)
  170 + return {}
  171 + end,
  172 + deactivate = function(self, t)
  173 + return true
  174 + end,
  175 + info = function(self, t)
  176 + return ([[By creating an arcane link with your minions army you are able to redirect parts of any damage affecting you to them.
  177 + Anytime you take damage %d%% of it is instead redirected to a random minion without your aura of undeath.
  178 + The minion takes 300%% damage from that effect.
  179 + The damage redirected percent depends on your Spellpower.]]):
  180 + tformat(t:_getPower(self))
  181 + end,
  182 +}
... ...
  1 +-- ToME - Tales of Maj'Eyal
  2 +-- Copyright (C) 2009 - 2020 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 +newTalent{
  21 + name = "Call of the Crypt",
  22 + type = {"spell/master-of-bones",1},
  23 + require = spells_req1,
  24 + points = 5,
  25 + fake_ressource = true,
  26 + mana = 5,
  27 + soul = function(self, t) return math.max(1, math.min(t.getMax(self, t), self:getSoul())) end,
  28 + cooldown = 14,
  29 + tactical = { ATTACK = 10 },
  30 + requires_target = true,
  31 + autolearn_talent = "T_SOUL_POOL",
  32 + range = 0,
  33 + minions_list = {
  34 + skel_warrior = {
  35 + type = "undead", subtype = "skeleton",
  36 + name = "skeleton warrior", color=colors.SLATE, image="npc/skeleton_warrior.png",
  37 + blood_color = colors.GREY,
  38 + display = "s", color=colors.SLATE,
  39 + combat = { dam=1, atk=1, apr=1 },
  40 + level_range = {1, nil}, exp_worth = 0,
  41 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
  42 + infravision = 10,
  43 + rank = 2,
  44 + size_category = 3,
  45 + autolevel = "warrior",
  46 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=4, },
  47 + stats = { str=14, dex=12, mag=10, con=12 },
  48 + resolvers.racial(),
  49 + resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 },
  50 + open_door = true,
  51 + cut_immune = 1,
  52 + blind_immune = 1,
  53 + fear_immune = 1,
  54 + see_invisible = 2,
  55 + poison_immune = 1,
  56 + undead = 1,
  57 + rarity = 1,
  58 + skeleton_minion = "warrior",
  59 +
  60 + max_life = resolvers.rngavg(90,100),
  61 + combat_armor = 5, combat_def = 1,
  62 + resolvers.equip{ {type="weapon", subtype="greatsword", autoreq=true} },
  63 + resolvers.talents{
  64 + T_STUNNING_BLOW={base=1, every=7, max=5},
  65 + T_WEAPON_COMBAT={base=1, every=7, max=10},
  66 + T_WEAPONS_MASTERY={base=1, every=7, max=10},
  67 + },
  68 + ai_state = { talent_in=1, },
  69 + },
  70 + a_skel_warrior = {
  71 + type = "undead", subtype = "skeleton",
  72 + name = "armoured skeleton warrior", color=colors.STEEL_BLUE, image="npc/armored_skeleton_warrior.png",
  73 + blood_color = colors.GREY,
  74 + display = "s", color=colors.STEEL_BLUE,
  75 + combat = { dam=1, atk=1, apr=1 },
  76 + level_range = {1, nil}, exp_worth = 0,
  77 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
  78 + infravision = 10,
  79 + rank = 2,
  80 + size_category = 3,
  81 + autolevel = "warrior",
  82 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=4, },
  83 + stats = { str=14, dex=12, mag=10, con=12 },
  84 + resolvers.racial(),
  85 + resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 },
  86 + open_door = true,
  87 + cut_immune = 1,
  88 + blind_immune = 1,
  89 + fear_immune = 1,
  90 + poison_immune = 1,
  91 + see_invisible = 2,
  92 + undead = 1,
  93 + rarity = 1,
  94 + skeleton_minion = "warrior",
  95 +
  96 + resolvers.inscriptions(1, "rune"),
  97 + resolvers.talents{
  98 + T_WEAPON_COMBAT={base=1, every=7, max=10},
  99 + T_WEAPONS_MASTERY={base=1, every=7, max=10},
  100 + T_ARMOUR_TRAINING={base=2, every=14, max=4},
  101 + T_SHIELD_PUMMEL={base=1, every=7, max=5},
  102 + T_RIPOSTE={base=3, every=7, max=7},
  103 + T_OVERPOWER={base=1, every=7, max=5},
  104 + },
  105 + resolvers.equip{ {type="weapon", subtype="longsword", autoreq=true}, {type="armor", subtype="shield", autoreq=true}, {type="armor", subtype="heavy", autoreq=true} },
  106 + ai_state = { talent_in=1, },
  107 + },
  108 + skel_m_archer = {
  109 + type = "undead", subtype = "skeleton",
  110 + name = "skeleton master archer", color=colors.LIGHT_UMBER, image="npc/master_skeleton_archer.png",
  111 + blood_color = colors.GREY,
  112 + display = "s",
  113 + combat = { dam=1, atk=1, apr=1 },
  114 + level_range = {1, nil}, exp_worth = 0,
  115 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
  116 + infravision = 10,
  117 + rank = 2,
  118 + size_category = 3,
  119 + autolevel = "warrior",
  120 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=4, },
  121 + stats = { str=14, dex=12, mag=10, con=12 },
  122 + resolvers.racial(),
  123 + resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 },
  124 + open_door = true,
  125 + cut_immune = 1,
  126 + blind_immune = 1,
  127 + fear_immune = 1,
  128 + poison_immune = 1,
  129 + see_invisible = 2,
  130 + undead = 1,
  131 + rarity = 1,
  132 + skeleton_minion = "archer",
  133 +
  134 + max_life = resolvers.rngavg(70,80),
  135 + combat_armor = 5, combat_def = 1,
  136 + resolvers.talents{ T_BOW_MASTERY={base=1, every=7, max=10}, T_WEAPON_COMBAT={base=1, every=7, max=10}, T_SHOOT=1, T_PINNING_SHOT=3, T_CRIPPLING_SHOT=3, },
  137 + ai_state = { talent_in=1, },
  138 + rank = 3,
  139 + autolevel = "archer",
  140 + resolvers.equip{ {type="weapon", subtype="longbow", autoreq=true}, {type="ammo", subtype="arrow", autoreq=true} },
  141 + },
  142 + skel_mage = {
  143 + type = "undead", subtype = "skeleton",
  144 + name = "skeleton mage", color=colors.LIGHT_RED, image="npc/skeleton_mage.png",
  145 + blood_color = colors.GREY,
  146 + display = "s",
  147 + combat = { dam=1, atk=1, apr=1 },
  148 + level_range = {1, nil}, exp_worth = 0,
  149 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
  150 + infravision = 10,
  151 + rank = 2,
  152 + size_category = 3,
  153 + autolevel = "warrior",
  154 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=4, },
  155 + stats = { str=14, dex=12, mag=10, con=12 },
  156 + resolvers.racial(),
  157 + resolvers.tmasteries{ ["technique/other"]=0.3, ["technique/2hweapon-offense"]=0.3, ["technique/2hweapon-cripple"]=0.3 },
  158 + open_door = true,
  159 + cut_immune = 1,
  160 + blind_immune = 1,
  161 + fear_immune = 1,
  162 + poison_immune = 1,
  163 + see_invisible = 2,
  164 + undead = 1,
  165 + rarity = 1,
  166 + skeleton_minion = "mage",
  167 +
  168 + max_life = resolvers.rngavg(50,60),
  169 + max_mana = resolvers.rngavg(70,80),
  170 + combat_armor = 3, combat_def = 1,
  171 + stats = { str=10, dex=12, cun=14, mag=14, con=10 },
  172 + resolvers.talents{ T_STAFF_MASTERY={base=1, every=10, max=5}, T_FLAME={base=1, every=7, max=5}, T_MANATHRUST={base=2, every=7, max=5} },
  173 + resolvers.equip{ {type="weapon", subtype="staff", autoreq=true} },
  174 + autolevel = "caster",
  175 + ai_state = { talent_in=1, },
  176 + },
  177 + },
  178 + radius = function(self, t) return self:getTalentRadius(self:getTalentFromId(self.T_NECROTIC_AURA)) end,
  179 + target = function(self, t) return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} end,
  180 + getNb = function(self, t, ignore)
  181 + return math.max(1, math.floor(self:combatTalentScale(t, 1, 3, "log")))
  182 + end,
  183 + getMax = function(self, t, ignore)
  184 + local max = math.max(1, math.floor(self:combatTalentScale(t, 1, 7)))
  185 + if ignore then return max end
  186 + return math.max(0, max - necroArmyStats(self).nb_skeleton)
  187 + end,
  188 + getLevel = function(self, t) return math.floor(self:combatScale(self:getTalentLevel(t), -6, 0.9, 2, 5)) end, -- -6 @ 1, +2 @ 5, +5 @ 8
  189 + on_pre_use = function(self, t) return math.min(t.getMax(self, t), self:getSoul()) >= 1 end,
  190 + action = function(self, t)
  191 + local nb = t:_getNb(self)
  192 + local max = t:_getMax(self)
  193 + nb = math.min(nb, max, self:getSoul())
  194 + if nb < 1 then return end
  195 + local lev = t.getLevel(self, t)
  196 +
  197 + -- Summon minions in a cone
  198 + local tg = self:getTalentTarget(t)
  199 + local x, y = self:getTarget(tg)
  200 + if not x or not y then return nil end
  201 + local possible_spots = {}
  202 + self:project(tg, x, y, function(px, py)
  203 + if not game.level.map:checkAllEntities(px, py, "block_move") then
  204 + possible_spots[#possible_spots+1] = {x=px, y=py}
  205 + end
  206 + end)
  207 + if #possible_spots == 0 then return end
  208 +
  209 + local use_ressource = not self:attr("zero_resource_cost") and not self:attr("force_talent_ignore_ressources")
  210 + for i = 1, nb do
  211 + local pos = rng.tableRemove(possible_spots)
  212 + if pos then
  213 + if use_ressource then self:incSoul(-1) end
  214 + necroSetupSummon(self, self:getTalentLevel(t) >= 3 and t.minions_list.a_skel_warrior or t.minions_list.skel_warrior, pos.x, pos.y, lev, true)
  215 + self.__call_crypt_count = (self.__call_crypt_count or 0) + 1
  216 + if self.__call_crypt_count == 3 then
  217 + self.__call_crypt_count = 0
  218 + if self:getTalentLevel(t) >= 5 then
  219 + local pos = rng.tableRemove(possible_spots)
  220 + if pos then necroSetupSummon(self, rng.percent(50) and t.minions_list.skel_mage or t.minions_list.skel_m_archer, pos.x, pos.y, lev, true) end
  221 + end
  222 + end
  223 + end
  224 + end
  225 +
  226 + if use_ressource then self:incMana(-util.getval(t.mana, self, t) * (100 + 2 * self:combatFatigue()) / 100) end
  227 + game:playSoundNear(self, "talents/spell_generic2")
  228 + return true
  229 + end,
  230 + info = function(self, t)
  231 + return ([[Call upon the battlefields of all to collect bones and fuse them with souls, combining them to create skeletal minions to do your bidding.
  232 + Up to %d skeleton warriors of level %d are summoned. Up to %d skeletons can be controlled at once.
  233 + At level 3 the summons become armoured skeletons warriors.
  234 + At level 5 every 3 summoned warriors a free skeleton mage or skeleton archer is also created (without costing a soul).
  235 + ]]):tformat(t:_getNb(self), math.max(1, self.level + t:_getLevel(self)), t:_getMax(self, true))
  236 + end,
  237 +}
  238 +
  239 +newTalent{
  240 + name = "Bone Wall",
  241 + type = {"spell/master-of-bones", 2},
  242 + require = spells_req2,
  243 + points = 5,
  244 + mana = 20,
  245 + cooldown = 18, fixed_cooldown = true,
  246 + range = 10,
  247 + tactical = { ATTACKAREA = {COLD = 1, DARKNESS=1}, DISABLE = {pin=1} },
  248 + requires_target = true,
  249 + target = function(self, t)
  250 + local halflength = math.floor(t.getLength(self,t)/2)
  251 + local block = function(_, lx, ly)
  252 + return game.level.map:checkAllEntities(lx, ly, "block_move")
  253 + end
  254 + return {type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block}
  255 + end,
  256 + getChance = function(self, t) return math.floor(self:combatTalentScale(t, 20, 50)) end,
  257 + getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
  258 + getLength = function(self, t) return 1 + math.floor(self:combatTalentScale(t, 3, 7)/2)*2 end,
  259 + getDamage = function(self, t) return self:combatTalentMindDamage(t, 3, 15) end,
  260 + on_pre_use = function(self, t) return necroArmyStats(self).nb_skeleton > 0 end,
  261 + action = function(self, t)
  262 + local tg = self:getTalentTarget(t)
  263 + local x, y, target = self:getTargetLimited(tg)
  264 + if not target or not target.summoner == self or not target.necrotic_minion or not target.skeleton_minion then return nil end
  265 +
  266 + target:die(self)
  267 +
  268 + local damage = self:spellCrit(t:_getDamage(self))
  269 + local chance = t:_getChance(self)
  270 + local radius = 1
  271 +
  272 + self:project(tg, x, y, function(px, py, tg, self)
  273 + local oe = game.level.map(px, py, Map.TERRAIN)
  274 + if not oe or oe.special then return end
  275 + if not oe or oe:attr("temporary") or game.level.map:checkAllEntities(px, py, "block_move") then return end
  276 + local e = mod.class.Object.new{
  277 + old_feat = oe,
  278 + name = _t"bone wall", image = "npc/iceblock.png",
  279 + desc = _t"a summoned wall of bones",
  280 + type = "wall",
  281 + display = '#', color=colors.GREY, back_color=colors.BLUE,
  282 + always_remember = true,
  283 + can_pass = {pass_wall=1},
  284 + does_block_move = true,
  285 + pass_projectile = true,
  286 + show_tooltip = true,
  287 + block_move = true,
  288 + block_sight = false,
  289 + temporary = t.getDuration(self, t),
  290 + x = px, y = py,
  291 + canAct = false,
  292 + dam = damage,
  293 + pin_chance = chance,
  294 + act = function(self)
  295 + local t = self.summoner:getTalentFromId(self.T_BONE_WALL)
  296 + local tg = {type="ball", range=0, radius=1, friendlyfire=false, talent=t, x=self.x, y=self.y}
  297 + self.summoner.__project_source = self
  298 + self.summoner:projectApply(tg, self.x, self.y, engine.Map.ACTOR, function(target)
  299 + if target.turn_procs.bone_wall then return end
  300 + target.turn_procs.bone_wall = true
  301 + target.turn_procs.bone_wall = true
  302 + engine.DamageType:get(engine.DamageType.FROSTDUSK).projector(self.summoner, target.x, target.y, engine.DamageType.FROSTDUSK, self.dam)
  303 + if target:canBe("pin") and rng.percent(self.pin_chance) then
  304 + target:setEffect(target.EFF_PINNED, 4, {apply_power=self.summoner:combatSpellpower()})
  305 + end
  306 + end, "hostile")
  307 + self.summoner.__project_source = nil
  308 + self:useEnergy()
  309 + self.temporary = self.temporary - 1
  310 + if self.temporary <= 0 then
  311 + game.level.map(self.x, self.y, engine.Map.TERRAIN, self.old_feat)
  312 + game.level:removeEntity(self)
  313 + game.level.map:updateMap(self.x, self.y)
  314 + game.nicer_tiles:updateAround(game.level, self.x, self.y)
  315 + end
  316 + end,
  317 + dig = function(src, x, y, old)
  318 + game.level:removeEntity(old, true)
  319 + return nil, old.old_feat
  320 + end,
  321 + summoner_gain_exp = true,
  322 + summoner = self,
  323 + }
  324 + e.tooltip = mod.class.Grid.tooltip
  325 + game.level:addEntity(e)
  326 + game.level.map(px, py, Map.TERRAIN, e)
  327 + -- game.nicer_tiles:updateAround(game.level, px, py)
  328 + -- game.level.map:updateMap(px, py)
  329 + end)
  330 + return true
  331 + end,
  332 + info = function(self, t)
  333 + return ([[Sacrifice one skeleton to turn it into a wall of bones of %d length for %d turns.
  334 + The wall is strong enough to block movement but projectiles and sight are not hampered.
  335 + Any foes adjacent to it takes %0.2f frostdusk damage and has %d%% chances to be pinned for 4 turns.
  336 + ]]):tformat(3 + math.floor(self:getTalentLevel(t) / 2) * 2, t:_getDuration(self), damDesc(self, DamageType.FROSTDUSK, t:_getDamage(self)), t:_getChance(self))
  337 + end,
  338 +}
  339 +
  340 +newTalent{
  341 + name = "Assemble",
  342 + type = {"spell/master-of-bones",3},
  343 + require = spells_req3,
  344 + points = 5,
  345 + cooldown = 20,
  346 + mana = 30,
  347 + minions_list = {
  348 + bone_giant = {
  349 + type = "undead", subtype = "giant",
  350 + blood_color = colors.GREY,
  351 + display = "K",
  352 + combat = { dam=resolvers.levelup(resolvers.mbonus(45, 20), 1, 1), atk=15, apr=10, dammod={str=0.8} },
  353 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
  354 + infravision = 10,
  355 + life_rating = 12,
  356 + max_stamina = 90,
  357 + rank = 2,
  358 + size_category = 4,
  359 + movement_speed = 1.5,
  360 + autolevel = "warrior",
  361 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=2, },
  362 + stats = { str=20, dex=12, mag=16, con=16 },
  363 + resists = { [DamageType.PHYSICAL] = 20, [DamageType.BLIGHT] = 20, [DamageType.COLD] = 50, },
  364 + open_door = 1,
  365 + no_breath = 1,
  366 + confusion_immune = 1,
  367 + poison_immune = 1,
  368 + blind_immune = 1,
  369 + fear_immune = 1,
  370 + stun_immune = 1,
  371 + is_bone_giant = true,
  372 + see_invisible = resolvers.mbonus(15, 5),
  373 + undead = 1,
  374 + name = "bone giant", color=colors.WHITE,
  375 + desc=_t[[A towering creature, made from the bones of dozens of dead bodies. It is covered by an unholy aura.]],
  376 + resolvers.nice_tile{image="invis.png", add_mos = {{image="npc/undead_giant_bone_giant.png", display_h=2, display_y=-1}}},
  377 + max_life = resolvers.rngavg(100,120),
  378 + level_range = {1, nil}, exp_worth = 0,
  379 + combat_armor = 20, combat_def = 0,
  380 + on_melee_hit = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  381 + melee_project = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  382 + resolvers.talents{ T_BONE_ARMOUR={base=3, every=10, max=5}, T_STUN={base=3, every=10, max=5}, },
  383 + },
  384 + h_bone_giant = {
  385 + type = "undead", subtype = "giant",
  386 + blood_color = colors.GREY,
  387 + display = "K",
  388 + combat = { dam=resolvers.levelup(resolvers.mbonus(45, 20), 1, 1), atk=15, apr=10, dammod={str=0.8} },
  389 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
  390 + infravision = 10,
  391 + life_rating = 12,
  392 + max_stamina = 90,
  393 + rank = 2,
  394 + size_category = 4,
  395 + movement_speed = 1.5,
  396 + autolevel = "warrior",
  397 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=2, },
  398 + stats = { str=20, dex=12, mag=16, con=16 },
  399 + resists = { [DamageType.PHYSICAL] = 20, [DamageType.BLIGHT] = 20, [DamageType.COLD] = 50, },
  400 + open_door = 1,
  401 + no_breath = 1,
  402 + confusion_immune = 1,
  403 + poison_immune = 1,
  404 + blind_immune = 1,
  405 + fear_immune = 1,
  406 + stun_immune = 1,
  407 + is_bone_giant = true,
  408 + see_invisible = resolvers.mbonus(15, 5),
  409 + undead = 1,
  410 + name = "heavy bone giant", color=colors.RED,
  411 + desc=_t[[A towering creature, made from the bones of hundreds of dead bodies. It is covered by an unholy aura.]],
  412 + resolvers.nice_tile{image="invis.png", add_mos = {{image="npc/undead_giant_heavy_bone_giant.png", display_h=2, display_y=-1}}},
  413 + level_range = {1, nil}, exp_worth = 0,
  414 + max_life = resolvers.rngavg(100,120),
  415 + combat_armor = 20, combat_def = 0,
  416 + on_melee_hit = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  417 + melee_project = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  418 + resolvers.talents{ T_BONE_ARMOUR={base=3, every=10, max=5}, T_THROW_BONES={base=4, every=10, max=7}, T_STUN={base=3, every=10, max=5}, },
  419 + },
  420 + e_bone_giant = {
  421 + type = "undead", subtype = "giant",
  422 + blood_color = colors.GREY,
  423 + display = "K",
  424 + combat = { dam=resolvers.levelup(resolvers.mbonus(45, 20), 1, 1), atk=15, apr=10, dammod={str=0.8} },
  425 + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
  426 + infravision = 10,
  427 + life_rating = 12,
  428 + max_stamina = 90,
  429 + rank = 2,
  430 + size_category = 4,
  431 + movement_speed = 1.5,
  432 + autolevel = "warrior",
  433 + ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=2, },
  434 + stats = { str=20, dex=12, mag=16, con=16 },
  435 + resists = { [DamageType.PHYSICAL] = 20, [DamageType.BLIGHT] = 20, [DamageType.COLD] = 50, },
  436 + open_door = 1,
  437 + no_breath = 1,
  438 + confusion_immune = 1,
  439 + poison_immune = 1,
  440 + blind_immune = 1,
  441 + fear_immune = 1,
  442 + stun_immune = 1,
  443 + is_bone_giant = true,
  444 + see_invisible = resolvers.mbonus(15, 5),
  445 + undead = 1,
  446 + name = "eternal bone giant", color=colors.GREY,
  447 + desc=_t[[A towering creature, made from the bones of hundreds of dead bodies. It is covered by an unholy aura.]],
  448 + resolvers.nice_tile{image="invis.png", add_mos = {{image="npc/undead_giant_eternal_bone_giant.png", display_h=2, display_y=-1}}},
  449 + level_range = {1, nil}, exp_worth = 0,
  450 + max_life = resolvers.rngavg(100,120),
  451 + combat_armor = 40, combat_def = 20,
  452 + on_melee_hit = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  453 + melee_project = {[DamageType.BLIGHT]=resolvers.mbonus(15, 5)},
  454 + autolevel = "warriormage",
  455 + resists = {all = 50},
  456 + resolvers.talents{ T_BONE_ARMOUR={base=5, every=10, max=7}, T_STUN={base=3, every=10, max=5}, T_SKELETON_REASSEMBLE=5, },
  457 + },
  458 + },
  459 + tactical = { ATTACK = 2 },
  460 + requires_target = true,
  461 + range = 10,
  462 + getLevel = function(self, t) return math.floor(self:combatScale(self:getTalentLevel(t), -6, 0.9, 2, 5)) end, -- -6 @ 1, +2 @ 5, +5 @ 8
  463 + on_pre_use = function(self, t) local stats = necroArmyStats(self) return stats.nb_skeleton >= 3 end,
  464 + action = function(self, t)
  465 + local stats = necroArmyStats(self)
  466 + if stats.nb_skeleton < 3 then return end
  467 + if stats.bone_giant then stats.bone_giant:die(self) end
  468 +
  469 + local list = {}
  470 + for _, m in ipairs(stats.list) do if m.skeleton_minion then list[#list+1] = m end end
  471 + table.sort(list, function(a, b)
  472 + local pa, pb = a.life / a.max_life, b.life / b.max_life
  473 + if pa == pb then return a.creation_turn < b.creation_turn end
  474 + return pa < pb
  475 + end)
  476 +
  477 + local lev = t.getLevel(self, t)
  478 + local pos
  479 + for i = 1, 3 do
  480 + local skel = table.remove(list)
  481 + if i == 1 then pos = {x=skel.x, y=skel.y} end
  482 + skel:die(self)
  483 + end
  484 +
  485 + local def = t.minions_list.bone_giant
  486 + if self:getTalentLevel(t) >= 6 then def = t.minions_list.e_bone_giant
  487 + elseif self:getTalentLevel(t) >= 3 then def = t.minions_list.h_bone_giant
  488 + end
  489 +
  490 + necroSetupSummon(self, def, pos.x, pos.y, lev, true)
  491 +
  492 + game:playSoundNear(self, "talents/spell_generic2")
  493 + return true
  494 + end,
  495 + info = function(self, t)
  496 + return ([[Every army of undead minions needs its spearhead. To that end you combine 3 skeleton minions into a bone giant of level %d.
  497 + The minions used are automatically selected by taking the weaker and older ones first and a Lord of Skull is never used.
  498 + At level 3 a heavy bone giant is created instead.
  499 + At level 6 an eternal bone giant is created instead.
  500 + Only one bone giant may be active at any time, casting this spell while one exists will destroy it and replace it with a new one.
  501 + ]]):
  502 + tformat(math.max(1, self.level + t:_getLevel(self)))
  503 + end,
  504 +}
  505 +
  506 +newTalent{
  507 + name = "Lord of Skulls",
  508 + type = {"spell/master-of-bones", 4},
  509 + require = spells_req4,
  510 + points = 5,
  511 + soul = function(self, t) return self:getTalentLevel(t) < 6 and 1 or 3 end,
  512 + mana = 50,
  513 + cooldown = 30,
  514 + tactical = { SPECIAL=10 },
  515 + getLife = function(self, t) return self:combatTalentScale(t, 30, 80) end,
  516 + range = 10,
  517 + target = function(self, t) return {type="hit", range=self:getTalentRange(t)} end,
  518 + ai_outside_combat = true,
  519 + onAIGetTarget = function(self, t)
  520 + local targets = {}
  521 + for _, act in pairs(game.level.entities) do
  522 + if act.summoner == self and act.necrotic_minion and act.skeleton_minion and self:hasLOS(act.x, act.y) and core.fov.distance(self.x, self.y, act.x, act.y) <= self:getTalentRange(t) then
  523 + targets[#targets+1] = act
  524 + end end
  525 + if #targets == 0 then return nil end
  526 + local tgt = rng.table(targets)
  527 + return tgt.x, tgt.y, tgt
  528 + end,
  529 + on_pre_use = function(self, t) return necroArmyStats(self).nb_skeleton > 0 end,
  530 + action = function(self, t, p)
  531 + local tg = self:getTalentTarget(t)
  532 + local x, y, target = self:getTargetLimited(tg)
  533 + if not x or not y or not target then return nil end
  534 + if not target.skeleton_minion or target.summoner ~= self then return nil end
  535 +
  536 + local stats = necroArmyStats(self)
  537 + if stats.lord_of_skulls then stats.lord_of_skulls:removeEffect(stats.lord_of_skulls.EFF_LORD_OF_SKULLS, false, true) end
  538 +
  539 + target:setEffect(target.EFF_LORD_OF_SKULLS, 1, {life=t:_getLife(self), talents=self:getTalentLevel(t) >= 6})
  540 + return true
  541 + end,
  542 + info = function(self, t)
  543 + return ([[Consume a soul to empower one of your skeleton, making it into a Lord of Skulls.
  544 + The Lord of Skulls gain %d%% more life, is instantly healed to full.
  545 + There can be only one active Lord of Skulls, casting this spell on an other skeleton removes the effect from the current one.
  546 + At level 6 it also gains a new talent:
  547 + - Warriors learn Giant Leap, a powerful jump attack that deals damage and dazes and impact and frees the skeleton from any stun, daze and pin effects they may have
  548 + - Archers learn Vital Shot, a devastating attack that can stun and cripple their foes
  549 + - Mages learn Meteoric Crash, a destructive spell that crushes and burns foes in a big radius for multiple turns
  550 + ]]):
  551 + tformat(t:_getLife(self))
  552 + end,
  553 +}
... ...
... ... @@ -141,9 +141,11 @@ newTalent{
141 141 name = "Spikes of Decrepitude",
142 142 type = {"spell/necrosis",4},
143 143 require = spells_req4,
144   - mode = "passive",
  144 + mode = "sustained",
145 145 points = 5,
146 146 radius = 10,
  147 + sustain_mana = 10,
  148 + cooldown = 10,
147 149 getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 70) end,
148 150 getReduce = function(self, t) return self:combatTalentLimit(t, 50, 8, 25) end,
149 151 callbackOnActBase = function(self, t)
... ... @@ -161,6 +163,12 @@ newTalent{
161 163 end
162 164 end
163 165 end,
  166 + activate = function(self, t)
  167 + return {}
  168 + end,
  169 + deactivate = function(self, t, p)
  170 + return true
  171 + end,
164 172 info = function(self, t)
165 173 return ([[Each turn you unleash dark powers through your runeskin.
166 174 For each rune you have a random foe in sight will be hit by a spike of decrepitude, dealing %0.2f frostdusk damage.
... ...
... ... @@ -120,53 +120,38 @@ spells_req_high5 = {
120 120
121 121 -------------------------------------------
122 122 -- Necromancer minions
123   -function necroGetNbSummon(self)
124   - local nb = 0
125   - if not game.party or not game.party:hasMember(self) then return 0 end
126   - -- Count party members
127   - for act, def in pairs(game.party.members) do
128   - if act.summoner and act.summoner == self and act.necrotic_minion then nb = nb + 1 end
  123 +function necroArmyStats(self)
  124 + local stats = {nb=0, nb_skeleton=0, nb_ghoul=0, list={}}
  125 + if not game.level then return stats end
  126 + if not game.party or not game.party:hasMember(self) then
  127 + for _, act in pairs(game.level.entities) do if act.summoner == self and act.necrotic_minion then
  128 + stats.nb = stats.nb + 1
  129 + if act.skeleton_minion then stats.nb_skeleton = stats.nb_skeleton + 1 end
  130 + if act.ghoul_minion then stats.nb_ghoul = stats.nb_ghoul + 1 end
  131 + if act.lord_of_skulls then stats.lord_of_skulls = act end
  132 + if act.is_bone_giant then stats.bone_giant = act end
  133 + stats.list[#stats.list+1] = act
  134 + end end
  135 + else
  136 + for act, _ in pairs(game.party.members) do if act.summoner == self and act.necrotic_minion then
  137 + stats.nb = stats.nb + 1
  138 + if act.skeleton_minion then stats.nb_skeleton = stats.nb_skeleton + 1 end
  139 + if act.ghoul_minion then stats.nb_ghoul = stats.nb_ghoul + 1 end
  140 + if act.lord_of_skulls then stats.lord_of_skulls = act end
  141 + if act.is_bone_giant then stats.bone_giant = act end
  142 + stats.list[#stats.list+1] = act
  143 + end end
129 144 end
130   - return nb
  145 + return stats
131 146 end
132 147
133   -function applyDarkEmpathy(self, m)
134   - if self:knowTalent(self.T_DARK_EMPATHY) then
135   - local t = self:getTalentFromId(self.T_DARK_EMPATHY)
136   - local perc = t.getPerc(self, t)
137   - for k, e in pairs(self.resists) do
138   - m.resists[k] = (m.resists[k] or 0) + e * perc / 100
139   - end
140   - for k, e in pairs(self.resists_cap) do
141   - m.resists_cap[k] = e
142   - end
143   - m.combat_physresist = m.combat_physresist + self:combatPhysicalResist() * perc / 100
144   - m.combat_spellresist = m.combat_spellresist + self:combatSpellResist() * perc / 100
145   - m.combat_mentalresist = m.combat_mentalresist + self:combatMentalResist() * perc / 100
146   -