diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua index 444a00cea5a2b9185c51243c48fc6621c8250a9e..461afdf5b805bb3cf4420fd896e34813987d4103 100644 --- a/game/modules/tome/class/Actor.lua +++ b/game/modules/tome/class/Actor.lua @@ -143,6 +143,9 @@ function _M:init(t, no_default) t.resists = t.resists or {} t.resists_cap = t.resists_cap or { all = 100 } t.resists_pen = t.resists_pen or {} + + -- Absorbs a percentage of damage + t.damage_affinity = t.damage_affinity or {} -- % Increase damage t.inc_damage = t.inc_damage or {} diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua index a9832dda37cbfbc4467e8d58a21f95f563abb431..58f344c83f3e60f9d405e231376988ed60422a6e 100644 --- a/game/modules/tome/data/damage_types.lua +++ b/game/modules/tome/data/damage_types.lua @@ -133,7 +133,14 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr) target:setEffect(target.EFF_SMEARED, 6, {src=src, power=smear/6}) dam = 0 end - + + -- affinity healing, we store it to apply it after damage is resolved + local affinity_heal = 0 + if target.damage_affinity then + local aff = (target.damage_affinity[type] or 0) / 100 + affinity_heal = (dam * aff) + end + -- Reduce damage with resistance if target.resists then local pen = 0 @@ -198,6 +205,12 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr) target:setEffect(target.EFF_ELEMENTAL_HARMONY, 5 + math.ceil(target:attr("elemental_harmony")), {power=target:attr("elemental_harmony"), type=type}) end end + + -- damage affinity healing + if not target.dead and affinity_heal > 0 then + target:heal(affinity_heal) + game.logSeen(target, "%s is healed by the %s%s#LAST# damage!", target.name:capitalize(), DamageType:get(type).text_color or "#aaaaaa#", DamageType:get(type).name) + end if dam > 0 and src.__projecting_for and src.__projecting_for.project_type and (src.__projecting_for.project_type.talent_id or src.__projecting_for.project_type.talent) and src.getTalentFromId and not src.__projecting_for.talent_on_hit_done then local t = src:getTalentFromId(src.__projecting_for.project_type.talent or src.__projecting_for.project_type.talent_id) @@ -1546,17 +1559,6 @@ newDamageType{ end, } --- Dredge Haste -newDamageType{ - name = "dredge frenzy", type = "DREDGE_FRENZY", - projector = function(src, x, y, type, dam) - local target = game.level.map(x, y, Map.ACTOR) - if target and target.dredge then - target:setEffect(target.EFF_SPEED, 3, {power=0.6}) - end - end, -} - newDamageType{ name = "devour life", type = "DEVOUR_LIFE", projector = function(src, x, y, type, dam) diff --git a/game/modules/tome/data/general/npcs/horror.lua b/game/modules/tome/data/general/npcs/horror.lua index a4a55676a6f773f59edef3aae5fd7c179d27fb9b..82c4e22e8f2be168dd4ac5c1d4bb4cbfe47c653b 100644 --- a/game/modules/tome/data/general/npcs/horror.lua +++ b/game/modules/tome/data/general/npcs/horror.lua @@ -44,7 +44,8 @@ newEntity{ newEntity{ base = "BASE_NPC_HORROR", name = "worm that walks", color=colors.SANDY_BROWN, - desc = [[A maggot-filled robe with a vaguely humanoid shape.]], + desc = [[A bulging rotten robe seems to tear at the seams, with masses of bloated worms spilling from out and around the moving form. Two arm-like appendages, each made up of overlapping mucous-drenched maggots, grasp tightly around the handles of bile-coated waraxes. + Each swing drips pestulant fluid before it, and each droplet writhes and wriggles in the air before splashing against the ground.]], level_range = {25, nil}, exp_worth = 1, rarity = 5, max_life = resolvers.rngavg(150,170), @@ -52,8 +53,9 @@ newEntity{ base = "BASE_NPC_HORROR", rank = 3, hate_regen = 1, vim_regen = 1, - - ai = "tactical", ai_state = { ai_move="move_dmap", talent_in=1, }, + + autolevel = "warriormage", + ai = "tactical", ai_state = { ai_move="move_dmap", talent_in=1, ally_compassion=0 }, ai_tactic = resolvers.tactic "melee", see_invisible = 100, @@ -61,10 +63,11 @@ newEntity{ base = "BASE_NPC_HORROR", stun_immune = 1, blind_immune = 1, disease_immune = 1, - move_others=true, - - resists = { [DamageType.PHYSICAL] = 50, [DamageType.BLIGHT] = 100, [DamageType.FIRE] = -50}, + resists = { [DamageType.PHYSICAL] = 50, [DamageType.ACID] = 100, [DamageType.BLIGHT] = 100, [DamageType.FIRE] = -50}, + damage_affinity = { [DamageType.BLIGHT] = 50 }, + no_auto_resists = true, + body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 }, resolvers.drops{chance=20, nb=1, {} }, resolvers.equip{ @@ -78,15 +81,15 @@ newEntity{ base = "BASE_NPC_HORROR", resolvers.inscriptions(1, {"regeneration infusion"}), resolvers.talents{ - --[Talents.T_BONE_GRAB]={base=4, every=10}, - [Talents.T_BLINDSIDE]={base=5, every=12}, + [Talents.T_WORM_ROT]={base=4, every=8}, + [Talents.T_EPIDEMIC]={base=4, every=8}, + [Talents.T_REND]={base=4, every=8}, + [Talents.T_ACID_STRIKE]={base=4, every=8}, + [Talents.T_BLOODLUST]={base=4, every=8}, + [Talents.T_RUIN]={base=4, every=8}, [Talents.T_CORRUPTED_STRENGTH]={base=3, every=15}, - [Talents.T_SLASH]={base=4, every=12}, - [Talents.T_FRENZY]={base=4, every=15}, - [Talents.T_REND]={base=4, every=12}, - [Talents.T_BLOODLUST]={base=3, every=12}, - [Talents.T_RUIN]={base=2, every=12}, - [Talents.T_SUMMON]=1, + + [Talents.T_BLINDSIDE]={base=3, every=12}, [Talents.T_WEAPON_COMBAT]={base=5, every=10, max=10}, [Talents.T_WEAPONS_MASTERY]={base=3, every=10, max=10}, @@ -96,14 +99,12 @@ newEntity{ base = "BASE_NPC_HORROR", on_takehit = function(self, value, src) if value >= (self.max_life * 0.1) then - self:forceUseTalent(self.T_SUMMON, {ignore_energy=true, ignore_cd=true}) + local t = self:getTalentFromId(self.T_WORM_ROT) + t.spawn_carrion_worm(self, self, t) + game.logSeen(self, "#LIGHT_RED#A carrion worm mass has spawned from %s' wounds!", self.name) end return value end, - - summon = { - {type="vermin", subtype="worms", name="carrion worm mass", number=1, hasxp=false}, - }, } newEntity{ base = "BASE_NPC_HORROR", @@ -474,11 +475,9 @@ newEntity{ base = "BASE_NPC_HORROR", }, } --- Temporal Horrors - newEntity{ base = "BASE_NPC_HORROR", - subtype = "temporal", - name = "temporal devourer", color=colors.CRIMSON, + subtype = "eldritch", + name = "devourer", color=colors.CRIMSON, desc = "A headless, round creature with stubby legs and arms. Its body seems to be all teeth.", level_range = {10, nil}, exp_worth = 1, rarity = 2, @@ -486,17 +485,27 @@ newEntity{ base = "BASE_NPC_HORROR", movement_speed = 0.8, size_category = 2, autolevel = "warrior", - max_life = resolvers.rngavg(50, 80), - combat_armor = 1, combat_def = 10, - combat = { dam=resolvers.levelup(resolvers.rngavg(20,30), 1, 1.2), atk=resolvers.rngavg(10,20), apr=5, dammod={str=1} }, + max_life = resolvers.rngavg(80, 100), + life_rating = 14, + life_regen = 4, + combat_armor = 16, combat_def = 1, + combat = { dam=resolvers.levelup(resolvers.rngavg(25,40), 1, 0.6), atk=resolvers.rngavg(25,50), apr=25, dammod={str=1.1}, physcrit = 10 }, + ai_state = { talent_in=2, }, - resists = { [DamageType.TEMPORAL] = 5}, + resolvers.talents{ + [Talents.T_BLOODBATH]={base=1, every=5, max=7}, + [Talents.T_GNASHING_TEETH]={base=1, every=5, max=7}, + -- talents only usable while frenzied + [Talents.T_FRENZIED_LEAP]={base=1, every=5, max=7}, + [Talents.T_FRENZIED_BITE]={base=1, every=5, max=7}, + }, make_escort = { - {type="horror", subtype="temporal", name="temporal devourer", number=2, no_subescort=true}, + {type="horror", subtype="eldritch", name="devourer", number=2, no_subescort=true}, }, } +-- temporal horrors newEntity{ base = "BASE_NPC_HORROR", subtype = "temporal", dredge = 1, @@ -511,7 +520,7 @@ newEntity{ base = "BASE_NPC_HORROR", combat_armor = 1, combat_def = 10, combat = { dam=resolvers.levelup(resolvers.rngavg(15,20), 1, 1.1), atk=resolvers.rngavg(5,15), apr=5, dammod={str=1} }, - resists = { [DamageType.TEMPORAL] = 5}, + resists = { [DamageType.TEMPORAL] = 25}, resolvers.talents{ [Talents.T_DUST_TO_DUST]={base=1, every=7, max=5}, @@ -523,7 +532,7 @@ newEntity{ base = "BASE_NPC_HORROR", newEntity{ base = "BASE_NPC_HORROR", subtype = "temporal", dredge = 1, - name = "temporal dredge", color=colors.PINK, + name = "dredge", color=colors.PINK, desc = "A hulking pink-skinned creature with long arms as thick as tree trunks. It drags its knuckles on the ground as it lumbers toward you.", level_range = {15, nil}, exp_worth = 1, rarity = 2, @@ -536,7 +545,7 @@ newEntity{ base = "BASE_NPC_HORROR", combat_armor = 1, combat_def = 0, combat = { dam=resolvers.levelup(resolvers.rngavg(25,150), 1, 1.2), atk=resolvers.rngavg(25,130), apr=1, dammod={str=1.1} }, - resists = {all = 10, [DamageType.TEMPORAL] = 50, [DamageType.PHYSICAL] = 25}, + resists = {all = 10, [DamageType.TEMPORAL] = 25, [DamageType.PHYSICAL] = 25}, resolvers.talents{ [Talents.T_STUN]={base=3, every=7, max=7}, @@ -565,7 +574,7 @@ newEntity{ base = "BASE_NPC_HORROR", resists = {all = 10, [DamageType.TEMPORAL] = 50}, make_escort = { - {type="horror", subtype="temporal", name="temporal dredge", number=3, no_subescort=true}, + {type="horror", subtype="temporal", name="dredge", number=3, no_subescort=true}, }, resolvers.inscriptions(1, {"shielding rune"}), diff --git a/game/modules/tome/data/general/npcs/vermin.lua b/game/modules/tome/data/general/npcs/vermin.lua index d3a6c6f16f1c71b0238612bf6510725e221c6093..b01681782bdf17420aecd94634585a108fc39229 100644 --- a/game/modules/tome/data/general/npcs/vermin.lua +++ b/game/modules/tome/data/general/npcs/vermin.lua @@ -66,15 +66,18 @@ newEntity{ base = "BASE_NPC_WORM", newEntity{ base = "BASE_NPC_WORM", name = "carrion worm mass", color=colors.SANDY_BROWN, + can_multiply = 2, level_range = {20, nil}, exp_worth = 1, rarity = 4, max_life = resolvers.rngavg(5,9), combat = { dam=1, atk=15, apr=100 }, - - resists = {[DamageType.BLIGHT] = 100}, + + disease_immune = 1, + resists = { [DamageType.PHYSICAL] = 50, [DamageType.ACID] = 100, [DamageType.BLIGHT] = 100, [DamageType.FIRE] = -50}, + no_auto_resists = true, autolevel = "warriormage", - resolvers.talents{ [Talents.T_CRAWL_POISON]=2, [Talents.T_ROTTING_DISEASE]=4, [Talents.T_MULTIPLY]=1 }, + resolvers.talents{ [Talents.T_CRAWL_ACID]=2, [Talents.T_ROTTING_DISEASE]=4, [Talents.T_MULTIPLY]=1 }, on_die = function(self, src) game.level.map:addEffect(self, @@ -82,7 +85,7 @@ newEntity{ base = "BASE_NPC_WORM", engine.DamageType.BLIGHT, self:getStr(90, true), 2, 5, nil, - engine.Entity.new{alpha=100, display='', color_br=30, color_bg=180, color_bb=60} + engine.Entity.new{alpha=50, display='', color_br=30, color_bg=180, color_bb=60} ) game.logSeen(self, "%s exudes a corrupted gas as it dies.", self.name:capitalize()) end, diff --git a/game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_temporal_devourer.png b/game/modules/tome/data/gfx/shockbolt/npc/horror_eldritch_devourer.png similarity index 100% rename from game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_temporal_devourer.png rename to game/modules/tome/data/gfx/shockbolt/npc/horror_eldritch_devourer.png diff --git a/game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_temporal_dredge.png b/game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_dredge.png similarity index 100% rename from game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_temporal_dredge.png rename to game/modules/tome/data/gfx/shockbolt/npc/horror_temporal_dredge.png diff --git a/game/modules/tome/data/talents/corruptions/plague.lua b/game/modules/tome/data/talents/corruptions/plague.lua index 0ef613a0d125630cf46ce13e26356cced74ca558..d8fcb47202d70938d166a6e5b3354d1479bb202c 100644 --- a/game/modules/tome/data/talents/corruptions/plague.lua +++ b/game/modules/tome/data/talents/corruptions/plague.lua @@ -212,7 +212,11 @@ newTalent{ local disease = rng.table(diseases) local params = disease.params params.src = self - target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con, heal_factor=disease.params.heal_factor}) + if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then + target:setEffect(disease.id, 6, {src=self, dam=disease.params.dam, str=disease.params.str, dex=disease.params.dex, con=disease.params.con, heal_factor=disease.params.heal_factor, burst=disease.params.burst}) + else + game.logSeen(target, "%s resists the disease!", target.name:capitalize()) + end game.level.map:particleEmitter(px, py, 1, "slime") end) end, diff --git a/game/modules/tome/data/talents/misc/horrors.lua b/game/modules/tome/data/talents/misc/horrors.lua index 313a571f072e720246bdf9e9e068735804983e32..625a6cb146d4f227e15fc0fb6630211f62ec4e55 100644 --- a/game/modules/tome/data/talents/misc/horrors.lua +++ b/game/modules/tome/data/talents/misc/horrors.lua @@ -19,11 +19,139 @@ newTalentType{ type="technique/horror", name = "horror techniques", hide = true, description = "Physical talents of the various horrors of the world." } newTalentType{ no_silence=true, is_spell=true, type="spell/horror", name = "horror spells", hide = true, description = "Spell talents of the various horrors of the world." } +newTalentType{ no_silence=true, is_spell=true, type="corruption/horror", name = "horror spells", hide = true, description = "Spell talents of the various horrors of the world." } newTalentType{ type="other/horror", name = "horror powers", hide = true, description = "Unclassified talents of the various horrors of the world." } local oldTalent = newTalent local newTalent = function(t) if type(t.hide) == "nil" then t.hide = true end return oldTalent(t) end +-- Bloated Horror Powers +-- Ideas; Mind Shriek (confusion, plus mental damge each turn), Hallucination (Clones that die on hit if the target makes a mental save) +-- Devourer Powers +newTalent{ + name = "Frenzied Bite", + type = {"technique/horror", 3}, + points = 5, + cooldown = 12, + stamina = 24, + tactical = { ATTACK = 1, DISABLE = 2 }, + message = "In a frenzy @Source@ bites at @Target@!", + on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_FRENZY) then return false end return true end, + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y or not target then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + + local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 1, 1.7), true) + if hit then + if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 5) and target:canBe("cut") then + target:setEffect(target.EFF_DEEP_WOUND, 5, {src=self, heal_factor=self:getTalentLevel(t) * 10, power=self:combatTalentWeaponDamage(t, 1.5, 3)/5}) + end + end + return true + end, + info = function(self, t) + return ([[A nasty bite that causes a deep wound, only usable in a frenzy.]]) + end, +} + +newTalent{ + name = "Frenzied Leap", -- modified ghoulish leap, only usable while in a frenzy + type = {"technique/horror", 1}, + points = 5, + cooldown = 5, + tactical = { CLOSEIN = 3 }, + direct_hit = true, + message = "@Source@ leaps forward in a frenzy!", + range = function(self, t) return math.floor(2 + self:getTalentLevel(t)) end, + requires_target = true, + on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_FRENZY) or self:attr("encased_in_ice") or self:attr("never_move") then return false end return true end, + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > self:getTalentRange(t) then return nil end + + local l = line.new(self.x, self.y, x, y) + local lx, ly = l() + local tx, ty = lx, ly + lx, ly = l() + while lx and ly do + if game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move", self) then break end + tx, ty = lx, ly + lx, ly = l() + end + + -- Find space + if game.level.map:checkEntity(tx, ty, Map.TERRAIN, "block_move", self) then return nil end + local fx, fy = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true}) + if not fx then + return + end + self:move(fx, fy, true) + + return true + end, + info = function(self, t) + return ([[Leap toward your target.]]) + end, +} + +newTalent{ + name = "Gnashing Teeth", + type = {"technique/horror", 1}, + points = 5, + cooldown = 3, + stamina = 8, + message = "@Source@ tries to bite @Target@ with razor sharp teeth!", + requires_target = true, + tactical = { ATTACK = 2 }, + do_devourer_frenzy = function(self, target, t) + game.logSeen(self, "The scent of blood sends the %ss into a frenzy!", self.name:capitalize()) + -- frenzy devourerers + local tg = {type="ball", range=0, radius=3, selffire=true, talent=t} + self:project(tg, target.x, target.y, function(px, py) + local target = game.level.map(px, py, engine.Map.ACTOR) + local reapplied = false + if target then + local actor_frenzy = false + if target.name == "devourer" then + actor_frenzy = true + end + if actor_frenzy then + -- silence the apply message if the target already has the effect + for eff_id, p in pairs(target.tmp) do + local e = target.tempeffect_def[eff_id] + if e.name == "Frenzy" then + reapplied = true + end + end + target:setEffect(target.EFF_FRENZY, math.floor(2 + self:getTalentLevel(t)), {crit = self:getTalentLevel(t) * 3, power=self:getTalentLevel(t) * 0.2, dieat=self:getTalentLevel(t) * 0.2}, reapplied) + end + end + end) + end, + action = function(self, t) + local tg = {type="hit", range=self:getTalentRange(t)} + local x, y, target = self:getTarget(tg) + if not x or not y or not target then return nil end + if math.floor(core.fov.distance(self.x, self.y, x, y)) > 1 then return nil end + local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true) + + if target:checkHit(self:combatAttackStr(), target:combatPhysicalResist(), 0, 95, 8 - self:getTalentLevel(t) / 2) and target:canBe("cut") then + target:setEffect(target.EFF_CUT, 5, {power=self:combatTalentWeaponDamage(t, 0.8, 1.5), src=self}) + t.do_devourer_frenzy(self, target, t) + else + game.logSeen(target, "%s resists the cut!", target.name:capitalize()) + end + + return true + end, + info = function(self, t) + return ([[Bites the target, potentially causing it to bleed. The blood will send the devourer into a frenzy (which in turn will frenzy nearby devourers).]]) + end, +} -- Nightmare Horror Powers newTalent{ @@ -45,7 +173,7 @@ newTalent{ if not x then return end - + local m = target:clone{ shader = "shadow_simulacrum", no_drops = true, @@ -63,7 +191,7 @@ newTalent{ mod.class.NPC.castAs(m) engine.interface.ActorAI.init(m, m) - + m.energy.value = 0 m.player = nil m.max_life = m.max_life / 2 @@ -75,7 +203,7 @@ newTalent{ m.seen_by = nil m.can_talk = nil m.clone_on_hit = nil - + -- Remove some talents local tids = {} for tid, _ in pairs(m.talents) do @@ -86,10 +214,10 @@ newTalent{ if t.mode == "sustained" and m:isTalentActive(t.id) then m:forceUseTalent(t.id, {ignore_energy=true}) end m.talents[t.id] = nil end - + -- nil the Inner Demons effect to squelch combat log spam m.tmp[m.EFF_INNER_DEMONS] = nil - + -- remove detrimental timed effects local effs = {} for eff_id, p in pairs(m.tmp) do @@ -98,7 +226,7 @@ newTalent{ effs[#effs+1] = {"effect", eff_id} end end - + while #effs > 0 do local eff = rng.tableRemove(effs) if eff[1] == "effect" then @@ -106,12 +234,12 @@ newTalent{ end end - + game.zone:addEntity(game.level, m, "actor", x, y) game.level.map:particleEmitter(x, y, 1, "shadow") game.logSeen(target, "%s's Inner Demon manifests!", target.name:capitalize()) - + end, action = function(self, t) local tg = {type="hit", range=self:getTalentRange(t), talent=t} @@ -121,13 +249,13 @@ newTalent{ if not x or not y then return nil end local target = game.level.map(x, y, Map.ACTOR) if not target then return nil end - + if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 15) and target:canBe("fear") then target:setEffect(target.EFF_INNER_DEMONS, t.getDuration(self, t), {src = self, chance=t.getChance(self, t), dam=t.getDamage(self, t)}) else game.logSeen(target, "%s resists the demons!", target.name:capitalize()) end - + return true end, info = function(self, t) @@ -156,13 +284,13 @@ newTalent{ if not x or not y then return nil end local target = game.level.map(x, y, Map.ACTOR) if not target then return nil end - + if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 15) and target:canBe("fear") then target:setEffect(target.EFF_WAKING_NIGHTMARE, t.getDuration(self, t), {src = self, chance=t.getChance(self, t), dam=t.getDamage(self, t)}) else game.logSeen(target, "%s resists the nightmare!", target.name:capitalize()) end - + return true end, info = function(self, t) @@ -211,7 +339,8 @@ newTalent{ return ([[An area of effect that causes darkness damage and reduces the lite radius and darkness resistance of those within.]]) end, } - +-- Temporal Stalker Powers +-- Ideas; Damage Shift >:) Give more temporal effects, especially Warden effects, raise AP and add Temporal Damage on hit to mimic weapon folding, give invis rune -- Void Horror Powers newTalent{ name = "Echoes From The Void", @@ -233,13 +362,13 @@ newTalent{ if not x or not y then return nil end local target = game.level.map(x, y, Map.ACTOR) if not target then return nil end - - if target:checkHit(self:combatMindpower(), target:combatMentalResist(), 0, 95, 15) and target:canBe("fear") then - target:setEffect(target.EFF_VOID_ECHOES, 6, {src=self, power=t.getDamage(self, t)}) + + if target:checkHit(src:combatMindpower(), target:combatMentalResist(), 0, 95, 15) and target:canBe("fear") then + target:setEffect(target.EFF_VOID_ECHOES, 6, {src= self, power=t.getDamage(self, t)}) else game.logSeen(target, "%s resists the void!", target.name:capitalize()) end - + game:playSoundNear(self, "talents/arcane") return true end, @@ -269,9 +398,9 @@ newTalent{ local _ _, tx, ty = self:canProject(tg, tx, ty) target = game.level.map(tx, ty, Map.ACTOR) if target == self then target = nil end - + if self:getTalentLevel(t) < 5 then self:setEffect(self.EFF_SUMMON_DESTABILIZATION, 500, {power=5}) end - + for i = 1, self:getTalentLevelRaw(t) do -- Find space local x, y = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true}) @@ -287,41 +416,41 @@ newTalent{ name = "void shard", faction = self.faction, desc = [[It looks like a small hole in the fabric of spacetime.]], stats = { str=22, dex=20, wil=15, con=15 }, - - --level_range = {self.level, self.level}, + + --level_range = {self.level, self.level}, exp_worth = 0, max_life = resolvers.rngavg(5,10), life_rating = 2, rank = 2, size_category = 1, - + autolevel = "summoner", - ai = "summoned", ai_real = "dumb_talented_simple", ai_state = { talent_in=2, ai_move="move_snake" }, + ai = "summoned", ai_real = "dumb_talented_simple", ai_state = { talent_in=2, ai_move="move_snake" }, combat_armor = 1, combat_def = 1, combat = { dam=resolvers.levelup(resolvers.mbonus(40, 15), 1, 1.2), atk=15, apr=15, dammod={wil=0.8}, damtype=DamageType.TEMPORAL }, on_melee_hit = { [DamageType.TEMPORAL] = resolvers.mbonus(20, 10), }, - + infravision = 10, no_breath = 1, fear_immune = 1, stun_immune = 1, confusion_immune = 1, silence_immune = 1, - + ai_target = {actor=target} } - + m.faction = self.faction - m.summoner = self + m.summoner = self m.summoner_gain_exp=true m.summon_time = t.getSummonTime(self, t) - + m:resolve() m:resolve(nil, true) m:forceLevelup(self.level) game.zone:addEntity(game.level, m, "actor", x, y) game.level.map:particleEmitter(x, y, 1, "summon") m:setEffect(m.EFF_TEMPORAL_DESTABILIZATION, 5, {src=self, dam=t.getDamage(self, t), explosion=self:spellCrit(t.getExplosion(self, t))}) - + end game:playSoundNear(self, "talents/spell_generic") return true @@ -329,4 +458,56 @@ newTalent{ info = function(self, t) return ([[Summons shards of explosive doom!]]) end, -} \ No newline at end of file +} + +-- Worm that Walks Powers +newTalent{ + name = "Worm Rot", + type = {"corruption/horror", 1}, + points = 5, + cooldown = 8, + vim = 10, + range = 6, + requires_target = true, + tactical = { ATTACK = 2, DISABLE = 4 }, + getBurstDamage = function(self, t) return self:combatTalentSpellDamage(t, 30, 300) end, + getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 50) end, + getHealReduction = function(self, t) return self:combatTalentSpellDamage(t, 20, 70) end, + getDuration = function(self, t) return 4 + math.ceil(self:getTalentLevel(t)) end, + proj_speed = 6, + spawn_carrion_worm = function (self, target, t) + local x, y = util.findFreeGrid(target.x, target.y, 10, true, {[Map.ACTOR]=true}) + if not x then + return + end + + local worm = {type="vermin", subtype="worms", name="carrion worm mass", number=1, hasxp=false} + local m = game.zone:makeEntity(game.level, "actor", worm, nil, true) + m:resolve() + m.faction = self.faction + + game.zone:addEntity(game.level, m, "actor", x, y) + end, + action = function(self, t) + local tg = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="bolt_slime"}} + 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, engine.Map.ACTOR) + if not target then return end + if target:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 95, 12 - self:getTalentLevel(t)) and target:canBe("disease") then + target:setEffect(target.EFF_WORM_ROT, 5, {src=self, dam=t.getDamage(self, t), burst=t.getBurstDamage(self, t)}) + else + game.logSeen(target, "%s resists the worm rot!", target.name:capitalize()) + end + game.level.map:particleEmitter(px, py, 1, "slime") + end) + game:playSoundNear(self, "talents/slime") + + return true + end, + info = function(self, t) + return ([[A terrible rotting disease that removes a beneficial physical effect and deals acid and blight damage each turn. If not cleared after a full five turn duration it will inflict extra damage and spawn a carrion worm mass.]]) + end, +} diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua index 49f125e15759265800771f5bb0f7b83870c99e31..9724983766efacb78e38730c4cc730d7c0ed0e2c 100644 --- a/game/modules/tome/data/talents/misc/npcs.lua +++ b/game/modules/tome/data/talents/misc/npcs.lua @@ -1119,7 +1119,7 @@ newTalent{ target = function(self, t) return {type="beam", range=self:getTalentRange(t), talent=t} end, - tactical = { ATTACK = 3 }, + tactical = { ATTACK = 2, ESCAPE = 2 }, action = function(self, t) local tg = self:getTalentTarget(t) local x, y = self:getTarget(tg) @@ -1418,7 +1418,7 @@ newTalent{ points = 5, cooldown = 6, tactical = { - BUFF = 10, + BUFF = 4, }, direct_hit = true, range = 0, @@ -1428,9 +1428,30 @@ newTalent{ end, action = function(self, t) local tg = self:getTalentTarget(t) - local grids = self:project(tg, self.x, self.y, DamageType.DREDGE_FRENZY) + self:project(tg, self.x, self.y, function(px, py) + local target = game.level.map(px, py, engine.Map.ACTOR) + local reapplied = false + if target then + local actor_frenzy = false + if target.dredge then + actor_frenzy = true + end + if actor_frenzy then + -- silence the apply message if the target already has the effect + for eff_id, p in pairs(target.tmp) do + local e = target.tempeffect_def[eff_id] + if e.name == "Frenzy" then + reapplied = true + end + end + target:setEffect(target.EFF_FRENZY, self:getTalentLevel(t), {crit = self:getTalentLevel(t), power=self:getTalentLevel(t) * 0.1, dieat=self:getTalentLevel(t) * 0.1}, reapplied) + end + end + end) + game.level.map:particleEmitter(self.x, self.y, tg.radius, "ball_light", {radius=tg.radius}) game:playSoundNear(self, "talents/arcane") + return true end, info = function(self, t) diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua index 05339dd7a629415f47eafe387e524a28a5d8c3dc..aaa1a959acacdf0013c78a4da57ead45571f8c9c 100644 --- a/game/modules/tome/data/timed_effects.lua +++ b/game/modules/tome/data/timed_effects.lua @@ -1506,7 +1506,7 @@ newEffect{ newEffect{ name = "EPIDEMIC", desc = "Epidemic", - long_desc = function(self, eff) return ("The target is infected by a disease, doing %0.2f blight damage per turn.\nEach non-disease blight damage done to it will spread the disease."):format(eff.dam) end, + long_desc = function(self, eff) return ("The target is infected by a disease, doing %0.2f blight damage per turn and reducig healing received by %d%%.\nEach non-disease blight damage done to it will spread the disease."):format(eff.dam, eff.heal_factor) end, type = "disease", status = "detrimental", parameters = {}, @@ -2744,13 +2744,10 @@ newEffect{ end end - for i = 1, 1 do - if #effs == 0 then break end + if #effs > 0 then local eff = rng.tableRemove(effs) - if eff[1] == "effect" then self:removeEffect(eff[2]) - known = true end end end, @@ -4531,4 +4528,94 @@ newEffect{ end end end, +} + +newEffect{ + name = "WORM_ROT", + desc = "Worm Rot", + long_desc = function(self, eff) return ("The target is infected with carrion worm larvae. Each turn it will lose one beneficial physical effect and %0.2f blight and acid damage will be inflicted.\nAfter five turns the disease will inflict %0.2f blight damage and spawn a carrion worm mass."):format(eff.dam, eff.burst) end, + type = "disease", + status = "detrimental", + parameters = {}, + on_gain = function(self, err) return "#Target# is afflicted by a terrible worm rot!" end, + on_lose = function(self, err) return "#Target# is free from the worm rot." end, + -- Damage each turn + on_timeout = function(self, eff) + self.worm_rot_timer = self.worm_rot_timer - 1 + + -- disease damage + if self:attr("purify_disease") then + self:heal(eff.dam) + else + DamageType:get(DamageType.BLIGHT).projector(eff.src, self.x, self.y, DamageType.BLIGHT, eff.dam, {from_disease=true}) + end + -- acid damage from the larvae + DamageType:get(DamageType.ACID).projector(eff.src, self.x, self.y, DamageType.ACID, eff.dam) + + local effs = {} + -- Go through all physical effects + for eff_id, p in pairs(self.tmp) do + local e = self.tempeffect_def[eff_id] + if e.status == "beneficial" and e.type == "physical" then + effs[#effs+1] = {"effect", eff_id} + end + end + -- remove a random physical effect + if #effs > 0 then + local eff = rng.tableRemove(effs) + if eff[1] == "effect" then + self:removeEffect(eff[2]) + end + end + + -- burst and spawn a worm mass + if self.worm_rot_timer == 0 then + DamageType:get(DamageType.BLIGHT).projector(eff.src, self.x, self.y, DamageType.BLIGHT, eff.burst, {from_disease=true}) + local t = eff.src:getTalentFromId(eff.src.T_WORM_ROT) + t.spawn_carrion_worm(eff.src, self, t) + game.logSeen(self, "#LIGHT_RED#A carrion worm mass bursts out of %s!", self.name:capitalize()) + self:removeEffect(self.EFF_WORM_ROT) + end + end, + activate = function(self, eff) + self.worm_rot_timer = 5 + end, + deactivate = function(self, eff) + self.worm_rot_timer = nil + end, +} + +newEffect{ + name = "FRENZY", + desc = "Frenzy", + long_desc = function(self, eff) return ("Increases global action speed by %d%% and physical crit by %d%%.\nAdditionally the target will continue to fight until it's hit points reach -%d%%."):format(eff.power * 100, eff.crit, eff.dieat * 100) end, + type = "physical", + status = "beneficial", + parameters = { power=0.1 }, + on_gain = function(self, err) return "#Target# goes into a killing frenzy.", "+Frenzy" end, + on_lose = function(self, err) return "#Target# calms down.", "-Frenzy" end, + on_merge = function(self, old_eff, new_eff) + -- use on merge so reapplied frenzy doesn't kill off creatures with negative life + old_eff.dur = new_eff.dur + old_eff.power = new_eff.power + old_eff.crit = new_eff.crit + return old_eff + end, + activate = function(self, eff) + eff.tmpid = self:addTemporaryValue("global_speed", eff.power) + eff.critid = self:addTemporaryValue("combat_physcrit", eff.crit) + eff.dieatid = self:addTemporaryValue("die_at", -self.max_life * eff.dieat) + end, + deactivate = function(self, eff) + -- check negative life first incase the creature has healing + if self.life <= 0 then + local sx, sy = game.level.map:getTileToScreen(self.x, self.y) + game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, rng.float(-2.5, -1.5), "Falls dead!", {255,0,255}) + game.logSeen(self, "%s dies when it's frenzy ends!", self.name:capitalize()) + self:die(self) + end + self:removeTemporaryValue("global_speed", eff.tmpid) + self:removeTemporaryValue("combat_physcrit", eff.critid) + self:removeTemporaryValue("die_at", eff.dieatid) + end, } \ No newline at end of file