diff --git a/game/engines/default/engine/Entity.lua b/game/engines/default/engine/Entity.lua index 4400a211e552b6529c11c6c2a4a0018f0ae68f43..ffbbdd1dda1046800a0f45c3bfe5d81a7ee5bc7b 100644 --- a/game/engines/default/engine/Entity.lua +++ b/game/engines/default/engine/Entity.lua @@ -940,6 +940,19 @@ function _M:addTemporaryValue(prop, v, noupdate) else base[prop] = (base[prop] or 0) + v end +-- print("addTmpVal", base, prop, v, " :=: ", #t, id, method) + elseif type(v) == "function" then + -- Only last works on functions + if true or method == "last" then + base["__tlast_"..prop] = base["__tlast_"..prop] or {[-1] = base[prop]} + local b = base["__tlast_"..prop] + b[id] = v + b = table.listify(b) + table.sort(b, function(a, b) return a[1] > b[1] end) + base[prop] = b[1] and b[1][2] + else + base[prop] = (base[prop] or 0) + v + end -- print("addTmpVal", base, prop, v, " :=: ", #t, id, method) else error("unsupported temporary value type: "..type(v).." :=: "..tostring(v).." (on key "..tostring(prop)..")") @@ -1041,6 +1054,22 @@ function _M:removeTemporaryValue(prop, id, noupdate) if not base[prop] then util.send_error_backtrace("Error removing property "..tostring(prop).." with value "..tostring(v).." : base[prop] is nil") return end base[prop] = base[prop] - v end +-- print("delTmpVal", prop, v, method) + elseif type(v) == "function" then + -- Only last works on functions + if true or method == "last" then + base["__tlast_"..prop] = base["__tlast_"..prop] or {} + local b = base["__tlast_"..prop] + b[id] = nil + b = table.listify(b) + table.sort(b, function(a, b) return a[1] > b[1] end) + base[prop] = b[1] and b[1][2] + if b[1] and b[1][1] == -1 then base["__tlast_"..prop][-1] = nil end + if not next(base["__tlast_"..prop]) then base["__tlast_"..prop] = nil end + else + if not base[prop] then util.send_error_backtrace("Error removing property "..tostring(prop).." with value "..tostring(v).." : base[prop] is nil") return end + base[prop] = base[prop] - v + end -- print("delTmpVal", prop, v, method) else if config.settings.cheat then diff --git a/game/engines/default/engine/interface/ActorTalents.lua b/game/engines/default/engine/interface/ActorTalents.lua index 4cfebb6bccc0672cfa26929fc8f575a05b977eba..a5ed4288c65bff1bfc26c694cd6ded422d8d0ebc 100644 --- a/game/engines/default/engine/interface/ActorTalents.lua +++ b/game/engines/default/engine/interface/ActorTalents.lua @@ -1140,9 +1140,13 @@ end function _M:talentParticles(p, ...) local Particles = require "engine.Particles" if not p.__tmpparticles then p.__tmpparticles = {} end + local ret = {} for _, ps in ipairs{...} do - p.__tmpparticles[#p.__tmpparticles+1] = self:addParticles(Particles.new(ps.type, 1, ps.args, ps.shader)) + local pp = self:addParticles(Particles.new(ps.type, 1, ps.args, ps.shader)) + p.__tmpparticles[#p.__tmpparticles+1] = pp + ret[#ret+1] = pp end + return unpack(ret) end --- Trigger a talent method diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index ef9465b819cb3f2f00b0ac8aaff0ddcf2333b601..440743211b1ba6caa3fa19ade79667582b3fdf8a 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -4554,6 +4554,15 @@ function _M:onWear(o, inven_id, bypass_set, silent) -- o.wielder.wielded = true end + if o.talents_add_levels_filters then + self.talents_add_levels_custom = self.talents_add_levels_custom or {} + for i = 1, #o.talents_add_levels_filters do + local id = util.uuid() + self.talents_add_levels_custom[id] = o.talents_add_levels_filters[i].filter + o.talents_add_levels_filters[i]._id = id + end + end + if o.talent_on_spell then self.talent_on_spell = self.talent_on_spell or {} for i = 1, #o.talent_on_spell do @@ -4626,6 +4635,14 @@ end function _M:onTakeoff(o, inven_id, bypass_set, silent) engine.interface.ActorInventory.onTakeoff(self, o, inven_id) + if o.talents_add_levels_filters then + self.talents_add_levels_custom = self.talents_add_levels_custom or {} + for i = 1, #o.talents_add_levels_filters do + local id = o.talents_add_levels_filters[i]._id + self.talents_add_levels_custom[id] = nil + end + end + if o.talent_on_spell then self.talent_on_spell = self.talent_on_spell or {} for i = 1, #o.talent_on_spell do @@ -6786,8 +6803,14 @@ end --- Called if a talent level is > 0 function _M:alterTalentLevelRaw(t, lvl) - if self.talents_add_levels and self.talents_add_levels[id] then lvl = lvl + self.talents_add_levels[id] end + if t.no_unlearn_last then return lvl end -- Those are dangerous, do not change them + if self.talents_add_levels and self.talents_add_levels[t.id] then lvl = lvl + self.talents_add_levels[t.id] end if self:attr("spells_bonus_level") and t.is_spell then lvl = lvl + self:attr("spells_bonus_level") end + if self.talents_add_levels_custom and next(self.talents_add_levels_custom) then + for id, filter in pairs(self.talents_add_levels_custom) do if type(filter) == "function" then + lvl = filter(self, t, lvl) or lvl + end end + end -- if self:attr("mind_bonus_level") and t.is_mind then lvl = lvl + self:attr("mind_bonus_level") end -- if self:attr("nature_bonus_level") and t.is_nature then lvl = lvl + self:attr("nature_bonus_level") end return lvl diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua index c92055cb1097c32faaf1a85cc9436725268db602..de09a044542b09db907a725502aa07a4176187c3 100644 --- a/game/modules/tome/class/Object.lua +++ b/game/modules/tome/class/Object.lua @@ -2055,6 +2055,18 @@ function _M:getTextualDesc(compare_with, use_actor) use_actor.__inscription_data_fake = nil end + if self.wielder and self.wielder.talents_add_levels then + for tid, lvl in pairs(self.wielder.talents_add_levels) do + local t = use_actor:getTalentFromId(tid) + desc:add(lvl < 0 and {"color","FIREBRICK"} or {"color","OLIVE_DRAB"}, ("Talent level: %+d %s."):tformat(lvl, t and t.name or "???"), {"color","LAST"}, true) + end + end + if self.talents_add_levels_filters then + for _, data in ipairs(self.talents_add_levels_filters) do + desc:add(data.detrimental and {"color","FIREBRICK"} or {"color","OLIVE_DRAB"}, ("Talent level: %s."):tformat(data.desc), {"color","LAST"}, true) + end + end + local talents = {} if self.talent_on_spell then for _, data in ipairs(self.talent_on_spell) do if data.talent then diff --git a/game/modules/tome/class/WorldNPC.lua b/game/modules/tome/class/WorldNPC.lua index 9ad69ba50d7f6ab864843f107255025e67f41829..d52f4cdad32a38a5c8e44d3f8507fba13ebf35b5 100644 --- a/game/modules/tome/class/WorldNPC.lua +++ b/game/modules/tome/class/WorldNPC.lua @@ -187,5 +187,5 @@ function _M:tooltip(x, y, seen_by) end function _M:die(src) - engine.interface.ActorLife.die(self, src) + return engine.interface.ActorLife.die(self, src) end diff --git a/game/modules/tome/class/interface/PartyDeath.lua b/game/modules/tome/class/interface/PartyDeath.lua index 5092cbf225adef4596bd559970520168bc740775..cca7a45e887e65599fe4b4a994dd69e998ed1d3b 100644 --- a/game/modules/tome/class/interface/PartyDeath.lua +++ b/game/modules/tome/class/interface/PartyDeath.lua @@ -26,6 +26,10 @@ module(..., package.seeall, class.make) function _M:onPartyDeath(src, death_note) if self.dead then if game.level:hasEntity(self) then game.level:removeEntity(self, true) end return true end + -- Die + death_note = death_note or {} + if not mod.class.Actor.die(self, src, death_note) then return end + -- Remove from the party if needed if self.remove_from_party_on_death then game.party:removeMember(self, true) @@ -34,12 +38,8 @@ function _M:onPartyDeath(src, death_note) game.party:setDeathTurn(self, game.turn) end - -- Die - death_note = death_note or {} - mod.class.Actor.die(self, src, death_note) - -- Was not the current player, just die - if game.player ~= self then return end + if game.player ~= self then return true end -- Check for any survivor that can be controlled local game_ender = not game.party:findSuitablePlayer() @@ -138,4 +138,5 @@ function _M:onPartyDeath(src, death_note) profile.chat.uc_ext:sendKillerLink(msg, short_msg, src) end end + return true end diff --git a/game/modules/tome/data/birth/classes/mage.lua b/game/modules/tome/data/birth/classes/mage.lua index e59263825c7ed7e3c8434b2b26930db9fccb4d09..ef7e303de2179e7c4ca525a818416a6f0857a201 100644 --- a/game/modules/tome/data/birth/classes/mage.lua +++ b/game/modules/tome/data/birth/classes/mage.lua @@ -255,16 +255,16 @@ newBirthDescriptor{ talents_types = { ["spell/master-of-bones"]={true, 0.3}, ["spell/master-of-flesh"]={true, 0.3}, - ["spell/master-necromancer"]={true, 0.3}, + ["spell/master-necromancer"]={false, 0.3}, ["spell/grave"]={true, 0.3}, ["spell/glacial-waste"]={true, 0.3}, - ["spell/rime-wraith"]={true, 0.3}, + ["spell/rime-wraith"]={false, 0.3}, ["spell/nightfall"]={true, 0.3}, ["spell/dreadmaster"]={true, 0.3}, - ["spell/age-of-dusk"]={true, 0.3}, + ["spell/age-of-dusk"]={false, 0.3}, ["spell/death"]={true, 0.3}, ["spell/animus"]={true, 0.3}, - ["spell/eradication"]={true, 0.3}, + ["spell/eradication"]={false, 0.3}, ["spell/spectre"]={true, 0.3}, ["spell/necrosis"]={true, 0.3}, ["cunning/survival"]={true, 0.0}, diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index 91791c504a6bce4cf9033d7a6f94229bcb29793b..5fcd7ec72f019df64fabe1dd0e72f1e4ec5ff0b1 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -4308,3 +4308,19 @@ newDamageType{ return realdam or 0 end, } + +newDamageType{ + name = _t"boneyard", type = "BONEYARD", text_color = "#GREY#", + projector = function(src, x, y, type, dam, state) + state = initState(state) + useImplicitCrit(src, state) + local target = game.level.map(x, y, Map.ACTOR) + if not target then return end + + if src:reactionToward(target) < 0 then + target:setEffect(target.EFF_BRITTLE_BONES, 1, {apply_power=src:combatSpellpower(), resist=dam.resist, cooldown=dam.cooldown}) + elseif target.summoner == src and target.necrotic_minion then + target:setEffect(target.EFF_BONEYARD, 1, {power=dam.power}) + end + end, +} diff --git a/game/modules/tome/data/general/objects/boss-artifacts-maj-eyal.lua b/game/modules/tome/data/general/objects/boss-artifacts-maj-eyal.lua index 27e25496fc831bcb93a362fce8940dfe963c7344..902ded6247ce8d73bd1ae43ca864e4125d18454f 100644 --- a/game/modules/tome/data/general/objects/boss-artifacts-maj-eyal.lua +++ b/game/modules/tome/data/general/objects/boss-artifacts-maj-eyal.lua @@ -1749,7 +1749,7 @@ newEntity{ base = "BASE_GAUNTLETS", define_as = "STORM_BRINGER_GAUNTLETS", inc_stats = { [Stats.STAT_MAG] = 6, }, combat_spellpower = 12, resists = { [DamageType.LIGHTNING] = 15, }, - inc_damage = { [DamageType.LIGHTNING] = 20 }, + inc_damage = { [DamageType.LIGHTNING] = 15 }, combat_spellcrit = 5, combat_critical_power = 20, combat_armor = 5, @@ -1760,10 +1760,20 @@ newEntity{ base = "BASE_GAUNTLETS", define_as = "STORM_BRINGER_GAUNTLETS", physspeed = 0.2, dammod = {dex=0.4, str=-0.6, cun=0.4 }, melee_project={ [DamageType.LIGHTNING] = 20, }, - talent_on_hit = { [Talents.T_CHAIN_LIGHTNING] = {level=3, chance=20}, [Talents.T_NOVA] = {level=2, chance=15} }, + talent_on_hit = { [Talents.T_CHAIN_LIGHTNING] = {level=2, chance=20}, [Talents.T_NOVA] = {level=1, chance=15} }, damrange = 0.3, }, }, + talents_add_levels_filters = { + {desc=_t"+1 to all lightning damage spells", filter=function(who, t, lvl) + if t.is_spell and t.tactical and ( + table.get(t.tactical, "attack", "LIGHTNING") or + table.get(t.tactical, "attackarea", "LIGHTNING") + ) then + return lvl + 1 + end + end}, + }, talent_on_spell = { {chance=10, talent=Talents.T_LIGHTNING, level=1}, }, diff --git a/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_archer.png b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_archer.png new file mode 100644 index 0000000000000000000000000000000000000000..713ed34cc420658f6b39a02447d67aecf8935822 Binary files /dev/null and b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_archer.png differ diff --git a/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_magus.png b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_magus.png new file mode 100644 index 0000000000000000000000000000000000000000..68a473b3f510883827062b143a88e59749e1d373 Binary files /dev/null and b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_magus.png differ diff --git a/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_warrior.png b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_warrior.png new file mode 100644 index 0000000000000000000000000000000000000000..9be69ee59bbff16824c013ac0bea307468385f0a Binary files /dev/null and b/game/modules/tome/data/gfx/shockbolt/npc/lord_of_skulls_warrior.png differ diff --git a/game/modules/tome/data/talents/spells/death.lua b/game/modules/tome/data/talents/spells/death.lua index c3af1defd6f6d13e1a1f19834430ca6713339ba5..d91e95c8aaa7783d37b7f6edcece56c1691371d5 100644 --- a/game/modules/tome/data/talents/spells/death.lua +++ b/game/modules/tome/data/talents/spells/death.lua @@ -204,37 +204,30 @@ newTalent{ points = 5, mode = "sustained", cooldown = 30, - sustain_mana = 30, - getNb = function(self, t) return math.floor(self:combatTalentScale(t, 2, 8)) end, - getMana = function(self, t) return math.floor(self:combatTalentScale(t, 5, 30)) / 10 end, - getSpellpower = function(self, t) return math.floor(self:combatTalentScale(t, 10, 40)) end, - getResists = function(self, t) return math.floor(self:combatTalentLimit(t, 20, 5, 10)) end, - callbackOnActBase = function(self, t) - if not self.__old_reaping_souls then self.__old_reaping_souls = self:getSoul() end - if self.__old_reaping_souls == self:getSoul() then return end - self:updateTalentPassives(t) - end, - passives = function(self, t, p) - if not self:isTalentActive(t.id) then return end - local s = self:getSoul() - if s >= 2 then self:talentTemporaryValue(p, "mana_regen", t.getMana(self, t)) end - if s >= 5 then self:talentTemporaryValue(p, "combat_spellpower", t.getSpellpower(self, t)) end - if s >= 8 then self:talentTemporaryValue(p, "resists", {all=t.getResists(self, t)}) end - self:talentTemporaryValue(p, "max_soul", t.getNb(self, t)) + sustain_mana = 15, + getMana = function(self, t) return math.floor(self:combatTalentScale(t, 10, 27)) end, + getDur = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end, + trigger = function(self, t) + self:incMana(t:_getMana(self)) + if self:getTalentLevel(t) >= 3 then + self:setEffect(self.EFF_DEATH_RUSH, t:_getDur(self), {power=0.5}) + end + end, + callbackOnKill = function(self, t, target, death_note) + t:_trigger(self) + end, + callbackOnSummonKill = function(self, t, minion, target, death_note) + t:_trigger(self) end, activate = function(self, t) - game:onTickEnd(function() self:updateTalentPassives(t) end) return {} end, deactivate = function(self, t) return true end, info = function(self, t) - return ([[You draw constant power from the souls you hold within your grasp. - If you hold at least 2, your mana regeneration is increased by %0.1f per turn. - If you hold at least 5, your spellpower is increased by %d. - If you hold at least 8, all your resistances are increased by %d. - Also increases your maximum souls capacity by %d.]]): - tformat(t.getMana(self, t), t.getSpellpower(self, t), t.getResists(self, t), t.getNb(self, t)) + return ([[Whenever a creature is killed by yourself or a minion you feast on its essence, gaining %0.1f mana. + At level 3 the thrill of the death invigorates you, granting a movement sped bonus of 50%% for %d turns.]]): + tformat(t.getMana(self, t), t.getDur(self, t)) end, } diff --git a/game/modules/tome/data/talents/spells/eradication.lua b/game/modules/tome/data/talents/spells/eradication.lua new file mode 100644 index 0000000000000000000000000000000000000000..3e5c6063125dcbcca9d8562872241c905362ae6f --- /dev/null +++ b/game/modules/tome/data/talents/spells/eradication.lua @@ -0,0 +1,228 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009 - 2020 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 + +newTalent{ + name = "Boneyard", + type = {"spell/eradication",1}, + require = spells_req_high1, + points = 5, + soul = 1, + mana = 30, + range = 0, + radius = 5, + cooldown = 20, + tactical = { BUFF = 2 }, + requires_target = true, + target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t} end, + getResist = function(self, t) return math.floor(self:combatTalentScale(t, 5, 15)) end, + getCooldown = function(self, t) return math.floor(self:combatTalentScale(t, 10, 30)) end, + getPower = function(self, t) return math.floor(self:combatTalentScale(t, 10, 55)) end, + getResurrect = function(self, t) return math.floor(self:combatTalentScale(t, 1, 9)) end, + callbackOnSummonDeath = function(self, t, summon, src, death_note) + if summon.summoner ~= self or not summon.necrotic_minion or summon.boneyard_resurrected then return end + local ok = false + for i, e in ipairs(game.level.map.effects) do + if e.damtype == DamageType.BONEYARD and e.src == self and e.grids[summon.x] and e.grids[summon.x][summon.y] then ok = true break end + end + if not ok then return end + + if summon.summon_time_max then summon.summon_time = math.ceil(summon.summon_time_max * 0.66) end + summon.boneyard_resurrected = true + game.logSeen(summon, "#GREY#%s is resurrected by the boneyard!", summon:getName():capitalize()) + return true + end, + action = function(self, t) + game.level.map:addEffect(self, + self.x, self.y, 8, + DamageType.BONEYARD, {resist=t:_getResist(self), cooldown=t:_getCooldown(self), power=t:_getPower(self), resurrect=t:_getResurrect(self)}, + self:getTalentRadius(t), + 5, nil, + {type="vapour"}, + nil, + true, true + ) + game:playSoundNear(self, "talents/skeleton") + return true + end, + info = function(self, t) + return ([[Spawn a boneyard of radius %d around you that lasts for 8 turns. + Any foes inside gain the brittle bones effect, reducing their physical resistance by %d%% and making all cooldowns %d%% longer. + When one of your minion stands in the boneyard they gain %d more physical and spell power. + At level 5 when a minion dies inside the boneyard it has %d%% chances to resurrect instantly. This effect may only happen once per minion. + ]]):tformat(self:getTalentRadius(t), t:_getResist(self), t:_getCooldown(self), t:_getPower(self), t:_getResurrect(self)) + end, +} + +newTalent{ + name = "To The Grave", + type = {"spell/eradication", 2}, + require = spells_req_high2, + points = 5, + cooldown = 20, + soul = 1, + mana = 28, + requires_target = true, + direct_hit = true, + range = 0, + radius = function(self, t) return math.floor(self:combatTalentScale(t, 3, 9)) end, + getNb = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end, + target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)} end, + on_pre_use = function(self, t) + for i, e in ipairs(game.level.map.effects) do + if e.damtype == DamageType.BONEYARD and e.src == self then return true end + end + end, + action = function(self, t) + local boneyard = nil + for i, e in ipairs(game.level.map.effects) do + if e.damtype == DamageType.BONEYARD and e.src == self then boneyard = e break end + end + if not boneyard then return end + + local tg = self:getTalentTarget(t) + local list = {} + self:projectApply(tg, self.x, self.y, Map.ACTOR, function(target, x, y) + list[#list+1] = {target=target, dist=core.fov.distance(target.x, target.y, boneyard.x, boneyard.y)} + end, "hostile") + if #list == 0 then return end + table.sort(list, "dist") + + local _, _, gs = util.findFreeGrid(boneyard.x, boneyard.y, boneyard.radius, true, {[Map.ACTOR]=true}) + if #gs == 0 then return true end + + while #gs > 0 and #list > 0 do + local foe = table.remove(list, 1).target + local spot = table.remove(gs, 1) + if foe:canBe("teleport") and target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 15) then + foe:forceMoveAnim(spot[1], spot[2]) + else + game.logSeen(foe, "%s resists the call of the boneyard!") + end + end + + local nb = t:_getNb(self) + local t_skeleton = self:getTalentFromId(self.T_CALL_OF_THE_CRYPT) + local t_ghoul = self:getTalentFromId(self.T_CALL_OF_THE_MAUSOLEUM) + while #gs > 0 and nb > 0 do + local spot = table.remove(gs, 1) + nb = nb - 1 + local m + if rng.percent(50) then + m = necroSetupSummon(self, t_ghoul.minions_list.ghoul, spot[1], spot[2], 0, 5, true) + else + m = necroSetupSummon(self, t_skeleton.minions_list.skel_warrior, spot[1], spot[2], 0, 5, true) + end + end + + return true + end, + info = function(self, t) + return ([[Teleport all foes in radius %d to your boneyard, as close to its center as possible. + Up to %d ghouls or skeletons are created around them by the boneyard, without any additional soul cost, but they only last 5 turns. + ]]):tformat(self:getTalentRadius(t), t:_getNb(self)) + end, +} + +newTalent{ + name = "Impending Doom", + type = {"spell/eradication", 3}, + require = spells_req_high3, + points = 5, + mana = 50, + soul = 1, + cooldown = 25, + tactical = { ATTACK = { COLD = 2, DARKNESS = 2 }, DISABLE = 2 }, + rnd_boss_restrict = function(self, t, data) return data.level < 15 end, + range = 7, + requires_target = true, + getMax = function(self, t) return 200 + self:combatTalentSpellDamage(t, 28, 850) end, + getDamage = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 100), 150, 50, 0, 117, 67) end, -- Limit damage factor to < 150% + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t), talent=t} + local x, y = self:getTarget(tg) + if not x or not y then return nil end + self:project(tg, x, y, function(px, py) + local target = game.level.map(px, py, Map.ACTOR) + if not target then return end + local dam = target.life * t.getDamage(self, t) / 100 + dam = math.min(dam, t.getMax(self, t)) + target:setEffect(target.EFF_IMPENDING_DOOM, 10, {apply_power=self:combatSpellpower(), dam=dam/10, src=self}) + end, 1, {type="freeze"}) + return true + end, + info = function(self, t) + return ([[Your target's doom draws near. Its healing factor is reduced by 80%%, and will take %d%% of its remaining life (or %0.2f, whichever is lower) over 10 turns as arcane damage. + This spell is so powerful that every 2 turns it tears a part of the target's soul, generating one soul for you. + The damage will increase with your Spellpower.]]): + tformat(t.getDamage(self, t), t.getMax(self, t)) + end, +} + +newTalent{ + name = "Eternal Night", + type = {"spell/eradication",4}, + require = spells_req_high4, + points = 5, + mode = "sustained", + sustain_mana = 50, + cooldown = 30, + tactical = { BUFF = 2 }, + getDamageIncrease = function(self, t) return self:combatTalentScale(t, 2.5, 10) end, + getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50, true) end, + getVampiric = function(self, t) return math.floor(self:combatTalentLimit(t, 60, 3, 8)) end, + callbackOnActBase = function(self, t) + local p = self:isTalentActive(t.id) if not p then return end + if p.cur_value > 0 then self:heal(p.cur_value, self) end + p.cur_value = 0 + end, + callbackOnDealDamage = function(self, t, value, target, dead, death_node) + if value <= 0 then return end + local p = self:isTalentActive(t.id) if not p then return end + p.cur_value = p.cur_value + value * t:_getVampiric(self) / 100 + end, + activate = function(self, t) + game:playSoundNear(self, "talents/spell_generic") + local ret = { cur_value = 0 } + self:talentTemporaryValue(ret, "inc_damage", {[DamageType.DARKNESS] = t.getDamageIncrease(self, t), [DamageType.COLD] = t.getDamageIncrease(self, t)}) + self:talentTemporaryValue(ret, "resists_pen", {[DamageType.DARKNESS] = t.getResistPenalty(self, t), [DamageType.COLD] = t.getResistPenalty(self, t)}) + + local particle + if core.shader.active(4) then + local p1, p2 = self:talentParticles(ret, + {type="shader_ring_rotating", args={rotation=0, radius=1.1, img="spinningwinds_black"}, shader={type="spinningwinds", ellipsoidalFactor={1,1}, time_factor=6000, noup=2.0, verticalIntensityAdjust=-3.0}}, + {type="shader_ring_rotating", args={rotation=0, radius=1.1, img="spinningwinds_black"}, shader={type="spinningwinds", ellipsoidalFactor={1,1}, time_factor=6000, noup=1.0, verticalIntensityAdjust=-3.0}} + ) + p1.toback = true + else + self:talentParticles(ret, {type="ultrashield", args={rm=0, rM=0, gm=0, gM=0, bm=10, bM=100, am=70, aM=180, radius=0.4, density=60, life=14, instop=20}}) + end + return ret + end, + deactivate = function(self, t, p) + return true + end, + info = function(self, t) + local damageinc = t.getDamageIncrease(self, t) + local ressistpen = t.getResistPenalty(self, t) + local affinity = t.getVampiric(self, t) + return ([[Surround yourself with Frostdusk, increasing all your darkness and cold damage by %0.1f%%, and ignoring %d%% of the darkness and cold resistance of your targets. + In addition, at the end of each turn you are healed for %d%% of all damage you dealt.]]) + :tformat(damageinc, ressistpen, affinity) + end, +} diff --git a/game/modules/tome/data/talents/spells/master-of-flesh.lua b/game/modules/tome/data/talents/spells/master-of-flesh.lua index 69f66eb37432781437c2c40da17a4595517372a0..faa4693167fa1284724491c36b7fd342aea95b00 100644 --- a/game/modules/tome/data/talents/spells/master-of-flesh.lua +++ b/game/modules/tome/data/talents/spells/master-of-flesh.lua @@ -132,13 +132,13 @@ newTalent{ summonGhoul = function(self, t, possible_spots, def) local pos = table.remove(possible_spots, 1) if pos then - necroSetupSummon(self, def, pos.x, pos.y, lev, t:_getTurns(self), true) + necroSetupSummon(self, def, pos.x, pos.y, t.getLevel(self, t), t:_getTurns(self), true) self.__call_mausoleum_count = (self.__call_mausoleum_count or 0) + 1 if self.__call_mausoleum_count == 4 then self.__call_mausoleum_count = 0 if self:getTalentLevel(t) >= 5 then local pos = table.remove(possible_spots, 1) - if pos then necroSetupSummon(self, t.minions_list.ghoulking, pos.x, pos.y, lev, t:_getTurns(self), true) end + if pos then necroSetupSummon(self, t.minions_list.ghoulking, pos.x, pos.y, t.getLevel(self, t), t:_getTurns(self), true) end end end return true diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua index 0707f350c4d52821cd655bc672d5a5e8c597eb46..7b02b85251d40b6f5f9d0e1b455b59d185612de7 100644 --- a/game/modules/tome/data/talents/spells/spells.lua +++ b/game/modules/tome/data/talents/spells/spells.lua @@ -152,7 +152,10 @@ function necroSetupSummon(self, def, x, y, level, turns, no_control) m.faction = self.faction m.summoner = self m.summoner_gain_exp = true - if turns then m.summon_time = turns end + if turns then + m.summon_time_max = turns + m.summon_time = turns + end m.exp_worth = 0 m.life_regen = 0 m.unused_stats = 0 @@ -308,6 +311,7 @@ load("/data/talents/spells/master-of-flesh.lua") load("/data/talents/spells/master-necromancer.lua") load("/data/talents/spells/animus.lua") load("/data/talents/spells/death.lua") +load("/data/talents/spells/eradication.lua") load("/data/talents/spells/necrosis.lua") load("/data/talents/spells/spectre.lua") diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua index 4dfaf38fdda267af95bef7e176675c3df7e9ab14..d614ad37a7eeca156c7407cd00c030a0b6b2b735 100644 --- a/game/modules/tome/data/timed_effects/magical.lua +++ b/game/modules/tome/data/timed_effects/magical.lua @@ -2001,7 +2001,7 @@ newEffect{ newEffect{ name = "IMPENDING_DOOM", image = "talents/impending_doom.png", desc = _t"Impending Doom", - long_desc = function(self, eff) return ("The target's final doom is drawing near, reducing healing factor by 80%% and dealing %0.2f arcane damage per turn. The effect will stop if the caster dies."):tformat(eff.dam) end, + long_desc = function(self, eff) return ("The target's final doom is drawing near, reducing healing factor by 80%% and dealing %0.2f frostdusk damage per turn. The effect will stop if the caster dies."):tformat(eff.dam) end, type = "magical", subtype = { arcane=true }, status = "detrimental", @@ -2010,10 +2010,16 @@ newEffect{ on_lose = function(self, err) return _t"#Target# is freed from the impending doom.", _t"-Doomed" end, activate = function(self, eff) eff.healid = self:addTemporaryValue("healing_factor", -0.8) + eff.soul_turn = false end, on_timeout = function(self, eff) if eff.src.dead or not game.level:hasEntity(eff.src) then return true end - DamageType:get(DamageType.ARCANE).projector(eff.src, self.x, self.y, DamageType.ARCANE, eff.dam) + DamageType:get(DamageType.FROSTDUSK).projector(eff.src, self.x, self.y, DamageType.FROSTDUSK, eff.dam) + eff.soul_turn = not eff.soul_turn + if eff.soul_turn then + eff.src:incSoul(1) + game.logSeen(self, "#CRIMSON#A piece of the soul of %s is torn apart by Impending Doom!", self:getName()) + end end, deactivate = function(self, eff) self:removeTemporaryValue("healing_factor", eff.healid) @@ -2035,6 +2041,21 @@ newEffect{ end, } +newEffect{ + name = "DEATH_RUSH", image = "talents/utterly_destroyed.png", + desc = _t"DEATH_RUSH", + long_desc = function(self, eff) return ("Movement speed increased by %d%%."):tformat(eff.power*100) end, + type = "magical", + subtype = { necrotic=true }, + status = "beneficial", + parameters = {power=0.5}, + on_gain = function(self, err) return _t"#Target# is invogorated by death!", true end, + on_lose = function(self, err) return _t"#Target# is less fast.", true end, + activate = function(self, eff) + self:effectTemporaryValue(eff, "movement_speed", eff.power) + end, +} + newEffect{ name = "ABYSSAL_SHROUD", image = "talents/abyssal_shroud.png", desc = _t"Abyssal Shroud", @@ -4734,6 +4755,18 @@ newEffect{ elseif self.skeleton_minion == "mage" then self:learnTalent(self.T_METEORIC_CRASH, true) end end + + local image + if self.skeleton_minion == "warrior" then image = "npc/lord_of_skulls_warrior.png" + elseif self.skeleton_minion == "archer" then image = "npc/lord_of_skulls_archer.png" + elseif self.skeleton_minion == "mage" then image = "npc/lord_of_skulls_magus.png" + end + + self.replace_display = mod.class.Actor.new{ + image = image, display_y = -1, display_h = 2 + } + self:removeAllMOs() + game.level.map:updateMap(self.x, self.y) end, deactivate = function(self, eff) self.lord_of_skulls = false @@ -4744,6 +4777,9 @@ newEffect{ end end self.name = self.old_los_name + self.replace_display = nil + self:removeAllMOs() + game.level.map:updateMap(self.x, self.y) end, } @@ -4821,3 +4857,35 @@ newEffect{ eff.turn_list = {} end, } + +newEffect{ + name = "BRITTLE_BONES", image = "talents/boneyard.png", + desc = _t"Brittle Bones", + long_desc = function(self, eff) return ("Physical resistance reduced by %d%% and talents cooldowns increased by %d%%."):tformat(eff.resist, eff.cooldown) end, + type = "magical", + subtype = { necrotic=true, resistance=true, cooldown=true }, + status = "detrimental", + parameters = {resist=10, cooldown=20}, + on_gain = function(self, err) return nil, true end, + on_lose = function(self, err) return nil, true end, + activate = function(self, eff) + self:effectTemporaryValue(eff, "resists", {[DamageType.PHYSICAL] = -eff.resist}) + self:effectTemporaryValue(eff, "talent_cd_reduction", {allpct = -eff.cooldown/100}) + end, +} + +newEffect{ + name = "BONEYARD", image = "talents/boneyard.png", + desc = _t"Boneyard", + long_desc = function(self, eff) return ("Spellpower and physical power increased by %d."):tformat(eff.power) end, + type = "magical", + subtype = { necrotic=true, power=true }, + status = "beneficial", + parameters = {power=10}, + on_gain = function(self, err) return nil, true end, + on_lose = function(self, err) return nil, true end, + activate = function(self, eff) + self:effectTemporaryValue(eff, "combat_spellpower", eff.power) + self:effectTemporaryValue(eff, "combat_dam", eff.power) + end, +}