diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua index e2debc27f6ba612cb395630329e4e586f4a43ae7..e90daeb47c0d2aa5a48d3dbb102e2d5d67a72a41 100644 --- a/game/engines/default/engine/Map.lua +++ b/game/engines/default/engine/Map.lua @@ -1310,6 +1310,19 @@ function _M:processEffects(update_shape_only) end end +function _M:removeEffect(e) + if e.particles then + for j, ps in ipairs(e.particles) do self:removeParticleEmitter(ps) end + end + if e.overlay then + self.z_effects[e.overlay.zdepth][e] = nil + end + for i, ee in ipairs(self.effects) do if ee == e then + table.remove(self.effects, i) + break + end end +end + --- Returns the first effect matching the given damage type, if any function _M:hasEffectType(x, y, type) for i, e in ipairs(self.effects) do diff --git a/game/engines/default/engine/interface/GameTargeting.lua b/game/engines/default/engine/interface/GameTargeting.lua index 32694d59a803ff2f7f666557376fea59f8417322..e2899db82ee83209fa7c07ce0ca68f701ef60307 100644 --- a/game/engines/default/engine/interface/GameTargeting.lua +++ b/game/engines/default/engine/interface/GameTargeting.lua @@ -186,7 +186,9 @@ function _M:targetMode(v, msg, co, typ) end if do_scan then local filter = nil - if not (type(typ) == "table" and typ.no_first_target_filter) then + if typ.custom_scan_filter then + filter = typ.custom_scan_filter + elseif not (type(typ) == "table" and typ.no_first_target_filter) then if type(typ) == "table" and typ.first_target and typ.first_target == "friend" then filter = function(a) return self.player:reactionToward(a) >= 0 end else diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 635111afbfd959c8391075ea137c3b9c019e686e..d5f84af155bf31fe17d89894ad7a02ab47617ac8 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -2968,8 +2968,10 @@ function _M:onTakeHit(value, src, death_note) end local cb = {value=value} - if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then - value = cb.value + if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then value = cb.value end + if self.summoner then + cb = {value=value} + if self.summoner:fireTalentCheck("callbackOnSummonHit", cb, self, src, death_note) then value = cb.value end end local hd = {"Actor:takeHit", value=value, src=src, death_note=death_note} @@ -5912,6 +5914,7 @@ local sustainCallbackCheck = { callbackOnTeleport = "talents_on_teleport", callbackOnDealDamage = "talents_on_deal_damage", callbackOnHit = "talents_on_hit", + callbackOnSummonHit = "talents_on_summon_hit", callbackOnAct = "talents_on_act", callbackOnActBase = "talents_on_act_base", callbackOnActEnd = "talents_on_act_end", diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index 02e662572abd7df341ab5b4352feb4473e9734d8..91791c504a6bce4cf9033d7a6f94229bcb29793b 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -4298,3 +4298,13 @@ newDamageType{ return (realdam1 or 0) + (realdam2 or 0) end, } + +newDamageType{ + name = _t"putrescent liquefaction", type = "PUTRESCENT_LIQUEFACTION", text_color = "#OLIVE_DRAB#", + projector = function(src, x, y, type, dam, state) + state = initState(state) + useImplicitCrit(src, state) + local realdam = DamageType:get(DamageType.FROSTDUSK).projector(src, x, y, DamageType.FROSTDUSK, dam, state) + return realdam or 0 + end, +} diff --git a/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua b/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua new file mode 100644 index 0000000000000000000000000000000000000000..c67e318100eab1fb65936bf24518af46459693f1 --- /dev/null +++ b/game/modules/tome/data/gfx/particles/pustulent_fulmination.lua @@ -0,0 +1,111 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009 - 2019 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 + +-------------------------------------------------------------------------------------- +-- Advanced shaders +-------------------------------------------------------------------------------------- +if core.shader.active(4) then +use_shader = {type="fireflash"} +base_size = 64 + +local nb = 0 + +local dir = 0 +if proj_x and src_x then + dir = math.deg(math.atan2(proj_y-src_y, proj_x-src_x)) +end + +return { + system_rotation = dir, system_rotationv = 0, + generator = function() + return { + life = 42, + --size = 30, sizev = 2.1*64*radius/16, sizea = 0, + size = 3.5*64*radius, sizev = 0, sizea = 0, + + x = 0, xv = 0, xa = 0, + y = 0, yv = 0, ya = 0, + dir = 0, dirv = dirv, dira = 0, + vel = 0, velv = 0, vela = 0, + + r = 1, rv = 0, ra = 0, + g = 1, gv = 0, ga = 0, + b = 1, bv = 0, ba = 0, + a = 1, av = 0, aa = 0, + } +end, }, +function(self) + if nb < 1 then + self.ps:emit(1) + end + nb = nb + 1 +end, +1, "particles_images/putrescent_pustules_fireflash" + + +-------------------------------------------------------------------------------------- +-- Default +-------------------------------------------------------------------------------------- +else +local nb = 0 +return { generator = function() + local radius = radius + local sradius = (radius + 0.5) * (engine.Map.tile_w + engine.Map.tile_h) / 2 + local ad = rng.float(0, 360) + local a = math.rad(ad) + local r = rng.float(sradius - 5, sradius) + local x = r * math.cos(a) + local y = r * math.sin(a) + local bx = math.floor(x / engine.Map.tile_w) + local by = math.floor(y / engine.Map.tile_h) + local static = rng.percent(40) + + return { + trail = 1, + life = 24, + size = 3, sizev = static and 0.05 or 0.15, sizea = 0, + + x = x, xv = 0, xa = 0, + y = y, yv = 0, ya = 0, + dir = static and a + math.rad(90 - rng.range(10, 20)) or a, dirv = 0, dira = 0, + vel = static and -2 or 0.5 * (-1-nb) * radius / 2.7, velv = 0, vela = static and -0.01 or rng.float(-0.3, -0.2) * 0.3, + + r = rng.range(0, 10)/255, rv = 0, ra = 0, + g = rng.range(200, 255)/255, gv = 0, ga = 0, + b = rng.range(120, 170)/255, bv = 0, ba = 0, + a = rng.range(25, 220)/255, av = static and -0.034 or 0, aa = 0.005, + } +end, }, +function(self) + if nb < 5 then + self.ps:emit(radius*266) + nb = nb + 1 + self.ps:emit(radius*266) + nb = nb + 1 + self.ps:emit(radius*266) + nb = nb + 1 + self.ps:emit(radius*266) + nb = nb + 1 + self.ps:emit(radius*266) + nb = nb + 1 + end +end, +5*radius*266 + +end diff --git a/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png b/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png new file mode 100644 index 0000000000000000000000000000000000000000..fd213ef149d9f4d5d0740eb45c921a8d12042dd9 Binary files /dev/null and b/game/modules/tome/data/gfx/particles_images/putrescent_pustules_fireflash.png differ diff --git a/game/modules/tome/data/talents/spells/master-of-bones.lua b/game/modules/tome/data/talents/spells/master-of-bones.lua index 6240e0598307e524495b900a02e691479580a6b7..06d43e797ca2cafa429d0cdcb6aa74454cffc2da 100644 --- a/game/modules/tome/data/talents/spells/master-of-bones.lua +++ b/game/modules/tome/data/talents/spells/master-of-bones.lua @@ -178,7 +178,7 @@ newTalent{ radius = function(self, t) return self:getTalentRadius(self:getTalentFromId(self.T_NECROTIC_AURA)) end, target = function(self, t) return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} end, getNb = function(self, t, ignore) - return math.max(1, math.floor(self:combatTalentScale(t, 1, 3, "log"))) + return math.max(1, math.floor(self:combatTalentScale(t, 1, 2, "log"))) end, getMax = function(self, t, ignore) local max = math.max(1, math.floor(self:combatTalentScale(t, 1, 7))) @@ -211,13 +211,13 @@ newTalent{ local pos = rng.tableRemove(possible_spots) if pos then if use_ressource then self:incSoul(-1) end - 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) + necroSetupSummon(self, self:getTalentLevel(t) >= 3 and t.minions_list.a_skel_warrior or t.minions_list.skel_warrior, pos.x, pos.y, lev, nil, true) self.__call_crypt_count = (self.__call_crypt_count or 0) + 1 if self.__call_crypt_count == 3 then self.__call_crypt_count = 0 if self:getTalentLevel(t) >= 5 then local pos = rng.tableRemove(possible_spots) - 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 + 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, nil, true) end end end end @@ -228,10 +228,12 @@ newTalent{ return true end, info = function(self, t) - 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. + return ([[Call upon the battle fields of old to collect bones and fuse them with souls, combining them to create skeletal minions to do your bidding. Up to %d skeleton warriors of level %d are summoned. Up to %d skeletons can be controlled at once. At level 3 the summons become armoured skeletons warriors. At level 5 every 3 summoned warriors a free skeleton mage or skeleton archer is also created (without costing a soul). + + #GREY##{italic}#Skeleton minions come in fewer numbers than ghoul minions but are generaly more durable.#{normal}# ]]):tformat(t:_getNb(self), math.max(1, self.level + t:_getLevel(self)), t:_getMax(self, true)) end, } @@ -251,12 +253,17 @@ newTalent{ local block = function(_, lx, ly) return game.level.map:checkAllEntities(lx, ly, "block_move") end - return {type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block} + return { + type="wall", range=self:getTalentRange(t), halflength=halflength, talent=t, halfmax_spots=halflength+1, block_radius=block, + nowarning=true, first_target="friend", custom_scan_filter=function(target) + return target.summoner == self and target.necrotic_minion and target.skeleton_minion + end, + } end, getChance = function(self, t) return math.floor(self:combatTalentScale(t, 20, 50)) end, getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end, getLength = function(self, t) return 1 + math.floor(self:combatTalentScale(t, 3, 7)/2)*2 end, - getDamage = function(self, t) return self:combatTalentMindDamage(t, 3, 15) end, + getDamage = function(self, t) return self:combatTalentSpellDamage(t, 3, 15) end, on_pre_use = function(self, t) return necroArmyStats(self).nb_skeleton > 0 end, action = function(self, t) local tg = self:getTalentTarget(t) @@ -491,7 +498,7 @@ newTalent{ elseif self:getTalentLevel(t) >= 3 then def = t.minions_list.h_bone_giant end - necroSetupSummon(self, def, pos.x, pos.y, lev, true) + necroSetupSummon(self, def, pos.x, pos.y, lev, nil, true) game:playSoundNear(self, "talents/spell_generic2") return true diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua index ec357ea0ea6822af970c831fdb1c0a50848f20bd..c72f8fde1908989e7c4e2be86f5fd47fa46c9d4a 100644 --- a/game/modules/tome/data/talents/spells/spells.lua +++ b/game/modules/tome/data/talents/spells/spells.lua @@ -145,13 +145,14 @@ function necroArmyStats(self) return stats end -function necroSetupSummon(self, def, x, y, level, no_control) +function necroSetupSummon(self, def, x, y, level, turns, no_control) local m = require("mod.class.NPC").new(def) m.necrotic_minion = true m.creation_turn = game.turn m.faction = self.faction m.summoner = self m.summoner_gain_exp = true + if turns then m.summon_time = turns end m.exp_worth = 0 m.life_regen = 0 m.unused_stats = 0 @@ -241,10 +242,19 @@ function necroSetupSummon(self, def, x, y, level, no_control) game.zone:addEntity(game.level, m, "actor", x, y) game.level.map:particleEmitter(x, y, 1, "summon") - -- m.on_die = function(self, killer) - -- if self.on_die_necrotic_minion then self:on_die_necrotic_minion(killer) end - -- local src = self.summoner - -- end + if m.ghoul_minion and self:knowTalent(self.T_PUTRESCENT_LIQUEFACTION) then + m.on_die = function(self, killer) + if not self.x or not game.level then return end + local src = self:resolveSource() + for i, e in ipairs(game.level.map.effects) do + if e.src == src and e.damtype == engine.DamageType.PUTRESCENT_LIQUEFACTION and e.grids[self.x] and e.grids[self.x][self.y] and src:isTalentActive(src.T_PUTRESCENT_LIQUEFACTION) then + local p = src:isTalentActive(src.T_PUTRESCENT_LIQUEFACTION) + p.dur = p.dur + src:callTalent(src.T_PUTRESCENT_LIQUEFACTION, "getIncrease") + game.logSeen(self, "#GREY#%s dissolves into the cloud of gore.", self:getName():capitalize()) + end + end + end + end -- Summons never flee m.ai_tactic = m.ai_tactic or {} @@ -294,6 +304,7 @@ load("/data/talents/spells/stone-alchemy.lua") load("/data/talents/spells/golem.lua") load("/data/talents/spells/master-of-bones.lua") +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/necrosis.lua") diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua index df343a9293e0245592acbf0a2cb8b7798d2a9ab3..5667a24f5eb8abf88f44094e989ecc3c14e41d05 100644 --- a/game/modules/tome/data/timed_effects/magical.lua +++ b/game/modules/tome/data/timed_effects/magical.lua @@ -4780,3 +4780,48 @@ newEffect{ if eff.src and eff.powerful then eff.src:resolveSource():incSoul(1) end end, } + +newEffect{ + name = "CORPSE_EXPLOSION", image = "talents/corpse_explosion.png", + desc = _t"Corpse Explosion", + long_desc = function(self, eff) return ("When a ghoul is hit or dies, it explodes, doing %0.2f frostdusk damage."):tformat(eff.damage) end, + type = "magical", + subtype = { necrotic=true, ghoul=true }, + status = "beneficial", + parameters = {damage=10, disease=5, radius=1}, + callbackPriorities={callbackOnSummonDeath = 100, callbackOnSummonHit = 100}, -- trigger after most others + callbackOnSummonDeath = function(self, eff, minion, killer, death_note) + local ed = self:getEffectFromId(self.EFF_CORPSE_EXPLOSION) + ed.registerHit(self, eff, minion) + end, + callbackOnSummonHit = function(self, eff, cb, minion, src, death_note) + if cb.value <= 0 then return end + local ed = self:getEffectFromId(self.EFF_CORPSE_EXPLOSION) + ed.registerHit(self, eff, minion) + end, + callbackOnActBase = function(self, eff) + if #eff.turn_list == 0 then return end + table.sort(eff.turn_list, function(a, b) return a.creation_turn < b.creation_turn end) + local m = eff.turn_list[1] + if m.x then + if not m.dead then m:die(self) end + game.logSeen(m, "#GREY#%s explodes in a blast of gore!", m:getName():capitalize()) + game.level.map:particleEmitter(m.x, m.y, eff.radius, "pustulent_fulmination", {radius=eff.radius}) + game:playSoundNear(m, "talents/slime") + m:project({type="ball", radius=eff.radius, friendlyfire=false}, m.x, m.y, DamageType.FROSTDUSK, self:spellCrit(eff.damage)) + local diseases = {{self.EFF_WEAKNESS_DISEASE, "str"}, {self.EFF_ROTTING_DISEASE, "con"}, {self.EFF_DECREPITUDE_DISEASE, "dex"}} + m:projectApply({type="ball", radius=eff.radius, friendlyfire=false}, m.x, m.y, Map.ACTOR, function(target) + local disease = rng.table(diseases) + game.log("============== %s", disease[1]) + target:setEffect(disease[1], 6, {src=self, dam=eff.damage / 6, [disease[2]]=eff.disease, apply_power=self:combatSpellpower()}) + end) + end + eff.turn_list = {} + end, + registerHit = function(self, eff, minion) + eff.turn_list[#eff.turn_list+1] = minion + end, + activate = function(self, eff) + eff.turn_list = {} + end, +}