diff --git a/game/modules/tome/data/birth/classes/rogue.lua b/game/modules/tome/data/birth/classes/rogue.lua index f7663e3089f8d3bfa58f8b62b59542b13258845f..424b5adbf19ceaa897aeb946d2100a647bac06e8 100644 --- a/game/modules/tome/data/birth/classes/rogue.lua +++ b/game/modules/tome/data/birth/classes/rogue.lua @@ -106,6 +106,7 @@ newBirthDescriptor{ ["cunning/lethality"]={true, 0.3}, ["cunning/dirty"]={true, 0.3}, ["cunning/shadow-magic"]={true, 0.3}, + ["cunning/ambush"]={false, 0.3}, }, talents = { [ActorTalents.T_DUAL_STRIKE] = 1, diff --git a/game/modules/tome/data/talents/cunning/ambush.lua b/game/modules/tome/data/talents/cunning/ambush.lua new file mode 100644 index 0000000000000000000000000000000000000000..f211d744e385c38b34edeafbf9be683e5772713a --- /dev/null +++ b/game/modules/tome/data/talents/cunning/ambush.lua @@ -0,0 +1,216 @@ +-- ToME - Tales of Maj'Eyal +-- Copyright (C) 2009, 2010, 2011 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 + +local Map = require "engine.Map" + +newTalent{ + name = "Shadow Leash", + type = {"cunning/ambush", 1}, + require = cuns_req_high1, + points = 5, + cooldown = 20, + stamina = 15, + mana = 15, + range = 1, + tactical = { DISABLE = 2 }, + requires_target = true, + getDuration = function(self, t) return 1 + math.ceil(self:getTalentLevel(t)) 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 + + if target:checkHit(self:combatAttackDex(), target:combatPhysicalResist(), 0, 95, 5) and target:canBe("disarm") then + target:setEffect(target.EFF_DISARMED, t.getDuration(self, t), {}) + else + game.logSeen(target, "%s resists the shadow!", target.name:capitalize()) + end + + return true + end, + info = function(self, t) + local duration = t.getDuration(self, t) + return ([[For an instant your weapons turn into a shadow leash that tries to grab the target's weapon, disarming it for %d tuns. + Duration increases with talent level and chance to suceed with your Dexterity stat.]]): + format(duration) + end, +} + +newTalent{ + name = "Shadow Ambush", + type = {"cunning/ambush", 2}, + require = cuns_req_high2, + points = 5, + cooldown = 20, + stamina = 15, + mana = 15, + range = 7, + tactical = { DISABLE = 2, CLOSEIN = 2 }, + requires_target = true, + getDuration = function(self, t) return 1 + math.ceil(self:getTalentLevel(t)) 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 + local _ _, x, y = self:canProject(tg, x, y) + target = game.level.map(x, y, Map.ACTOR) + if target == self then target = nil end + + local sx, sy = util.findFreeGrid(self.x, self.y, 5, true, {[engine.Map.ACTOR]=true}) + if not sx then return end + + target:move(sx, sy, true) + + if math.floor(core.fov.distance(self.x, self.y, sx, sy)) <= 1 then + if target:checkHit(self:combatAttackDex(), target:combatMentalResist(), 0, 95, 5) and target:canBe("silence") then + target:setEffect(target.EFF_SILENCED, t.getDuration(self, t), {}) + else + game.logSeen(target, "%s resists the shadow!", target.name:capitalize()) + end + end + + return true + end, + info = function(self, t) + local duration = t.getDuration(self, t) + return ([[You reach out with shadow vines toward your target, pulling it to you and silencing it for %d turns. + Duration increases with talent level and chance to suceed with your Dexterity stat.]]): + format(duration) + end, +} + +newTalent{ + name = "Ambuscade", + type = {"cunning/ambush", 3}, + points = 5, + cooldown = 10, + stamina = 35, + mana = 35, + require = cuns_req_high3, + requires_target = true, + tactical = { ATTACK = 3 }, + getStealthPower = function(self, t) return 25 + self:getCun(15, true) * self:getTalentLevel(t) end, + getDuration = function(self, t) return math.floor(3 + self:getTalentLevel(t)) end, + getHealth = function(self, t) return 0.2 + self:combatTalentSpellDamage(t, 20, 500) / 1000 end, + getDam = function(self, t) return 0.4 + self:combatTalentSpellDamage(t, 10, 500) / 1000 end, + action = function(self, t) + -- Find space + local x, y = util.findFreeGrid(self.x, self.y, 1, true, {[Map.ACTOR]=true}) + if not x then + game.logPlayer(self, "Not enough space to invoke your shadow!") + return + end + + local m = self:clone{ + shader = "shadow_simulacrum", + no_drops = true, + faction = self.faction, + summoner = self, summoner_gain_exp=true, + summon_time = t.getDuration(self, t), + ai_target = {actor=nil}, + ai = "summoned", ai_real = "tactical", + name = "Shadow of "..self.name, + desc = [[A dark shadowy shape who's form resembles you.]], + } + m:removeAllMOs() + m.make_escort = nil + m.on_added_to_level = nil + + m.energy.value = 0 + m.player = nil + m.max_life = m.max_life * t.getHealth(self, t) + m.life = util.bound(m.life, 0, m.max_life) + m.forceLevelup = function() end + m.on_die = nil + m.on_acquire_target = nil + m.seen_by = nil + m.can_talk = nil + m.clone_on_hit = nil + m.stealth = t.getStealthPower(self, t) + for i = 1, 10 do + m:unlearnTalent(m.T_AMBUSCADE) + m:unlearnTalent(m.T_STEALTH) + m:unlearnTalent(m.T_HIDE_IN_PLAIN_SIGHT) + end + m.remove_from_party_on_death = true + m.resists = { [DamageType.LIGHT] = -100, [DamageType.DARKNESS] = 130, all=-30 } + m.inc_damage.all = ((100 + (m.inc_damage.all or 0)) * t.getDam(self, t)) - 100 + + game.zone:addEntity(game.level, m, "actor", x, y) + game.level.map:particleEmitter(x, y, 1, "shadow") + + if game.party:hasMember(self) then + game.party:addMember(m, { + control="full", + type="shadow", + title="Shadow of "..self.name, + temporary_level=1, + orders = {target=true}, + on_control = function(self) + self.summoner.ambuscade_ai = self.summoner.ai + self.summoner.ai = "none" + end, + on_uncontrol = function(self) + self.summoner.ai = self.summoner.ambuscade_ai + game:onTickEnd(function() game.party:removeMember(self) end) + end, + }) + end + game:onTickEnd(function() game.party:setPlayer(m) end) + + game:playSoundNear(self, "talents/spell_generic2") + return true + end, + info = function(self, t) + return ([[You take full control of your own shadow for %d turns. + Your shadow possesses your talents and stats, has %d%% life and deals %d%% damage, -30%% all resistances, -100%% light resistance and 100%% darkness resistance. + Your shadow is permanently stealthed (%d power) + If you release control early your shadow will dissipate.]]): + format(t.getDuration(self, t), t.getHealth(self, t) * 100, t.getDam(self, t) * 100, t.getStealthPower(self, t)) + end, +} + +newTalent{ + name = "Shadow Veil", + type = {"cunning/ambush", 4}, + points = 5, + cooldown = 18, + stamina = 30, + mana = 60, + require = cuns_req_high4, + requires_target = true, + tactical = { ATTACK = 2, DEFEND = 1 }, + getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.9, 2) end, + getDuration = function(self, t) return 3 + math.ceil(self:getTalentLevel(t)) end, + getDamageRes = function(self, t) return 10 + self:getTalentLevel(t) * 5 end, + action = function(self, t) + self:setEffect(self.EFF_SHADOW_VEIL, t.getDuration(self, t), {res=t.getDamageRes(self, t), dam=t.getDamage(self, t)}) + return true + end, + info = function(self, t) + local damage = t.getDamage(self, t) + local duration = t.getDuration(self, t) + local res = t.getDamageRes(self, t) + return ([[You veil yourself in shadows for %d turns and let them control you. + While in the veil you become immune to status effects, gain %d%% all damage reduction and each turn you blink to a nearby foe, hitting it for %d%% darkness weapon damage. + While this goes on you can not be stopped unless you are killed and can not control you character.]]): + format(duration, res, 100 * damage) + end, +} diff --git a/game/modules/tome/data/talents/cunning/cunning.lua b/game/modules/tome/data/talents/cunning/cunning.lua index 0da20930d47c6f40c25b0f8e563a2a26ff2d54c1..a7ab9e0fb164b43f58ec2436dde3509dfdbef2dd 100644 --- a/game/modules/tome/data/talents/cunning/cunning.lua +++ b/game/modules/tome/data/talents/cunning/cunning.lua @@ -27,6 +27,7 @@ newTalentType{ allow_random=true, type="cunning/poisons-effects", name = "poison newTalentType{ allow_random=true, type="cunning/dirty", name = "dirty fighting", description = "Teaches various talents to cripple your foes." } newTalentType{ allow_random=true, type="cunning/lethality", name = "lethality", description = "How to make your foes feel the pain." } newTalentType{ allow_random=true, type="cunning/shadow-magic", name = "shadow magic", description = "Blending magic and shadows." } +newTalentType{ allow_random=true, type="cunning/ambush", name = "ambush", description = "Using darkness and a bit of magic you manipulate the shadows." } newTalentType{ allow_random=true, type="cunning/survival", name = "survival", generic = true, description = "The knowledge of the dangers of the world, and how to best avoid them." } newTalentType{ allow_random=true, type="cunning/tactical", name = "tactical", description = "Tactical combat abilities." } @@ -80,3 +81,4 @@ load("/data/talents/cunning/lethality.lua") load("/data/talents/cunning/tactical.lua") load("/data/talents/cunning/survival.lua") load("/data/talents/cunning/shadow-magic.lua") +load("/data/talents/cunning/ambush.lua") diff --git a/game/modules/tome/data/talents/spells/phantasm.lua b/game/modules/tome/data/talents/spells/phantasm.lua index b943575baa809d45a19d6383e0c2e9188d8c5621..de676820eaa55a7f13b04b08b5cd9b21459437fc 100644 --- a/game/modules/tome/data/talents/spells/phantasm.lua +++ b/game/modules/tome/data/talents/spells/phantasm.lua @@ -126,7 +126,7 @@ newTalent{ game:playSoundNear(self, "talents/heal") local ret = { invisible = self:addTemporaryValue("invisible", t.getInvisibilityPower(self, t)), - drain = self:addTemporaryValue("mana_regen", - math.max(2, 7 - self:getTalentLevelRaw(t))), + drain = self:addTemporaryValue("mana_regen", - math.max(2, 7 - math.ceil(self:getTalentLevelRaw(t) / 2))), } self:resetCanSeeCacheOf() return ret @@ -143,6 +143,6 @@ newTalent{ Beware, you should take off your light, otherwise you will still be easily spotted. This powerful spell constantly drains your %d mana while active. The bonus will increase with the Magic stat]]): - format(invisi, math.max(2, 7 - self:getTalentLevelRaw(t))) + format(invisi, math.max(2, 7 - math.ceil(self:getTalentLevelRaw(t) / 2))) end, } diff --git a/game/modules/tome/data/talents/spells/shades.lua b/game/modules/tome/data/talents/spells/shades.lua index 65d33012c4a7573f222d437f90c3e2a49accec8d..fe880dec58a99d56fe8fcb82982c91b7f4516212 100644 --- a/game/modules/tome/data/talents/spells/shades.lua +++ b/game/modules/tome/data/talents/spells/shades.lua @@ -185,6 +185,7 @@ newTalent{ summon_time = t.getDuration(self, t), ai_target = {actor=nil}, ai = "summoned", ai_real = "tactical", + name = "Forgery of Haze ("..self.name..")", desc = [[A dark shadowy shape who's form resembles you.]], } m:removeAllMOs() diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua index 73e3f7b32c9016f632714dd94b16dca639288eb4..c3f6f1904eca3b982605c221419725541d832d04 100644 --- a/game/modules/tome/data/timed_effects.lua +++ b/game/modules/tome/data/timed_effects.lua @@ -4387,3 +4387,40 @@ newEffect{ self:removeTemporaryValue("inc_necrotic_minions", eff.tmpid) end, } + +newEffect{ + name = "SHADOW_VEIL", + desc = "Shadow Veil", + long_desc = function(self, eff) return ("You veil yourself in shadows and let them control you. While in the veil you become immune to status effects, gain %d%% all damage reduction and each turn you blink to a nearby foe, hitting it for %d%% darkness weapon damage. While this goes on you can not be stopped unless you are killed and can not control you character."):format(eff.res, eff.dam * 100) end, + type = "time", + status = "beneficial", + parameters = { res=10, dam=1.5 }, + on_gain = function(self, err) return "#Target# is covered in a veil of shadows!", "+Assail" end, + on_lose = function(self, err) return "#Target# is no longer covered by shadows.", "-Assail" end, + activate = function(self, eff) + eff.sefid = self:addTemporaryValue("negative_status_effect_immune", 1) + eff.resid = self:addTemporaryValue("resists", {all=eff.res}) + end, + on_timeout = function(self, eff) + -- Choose a target in FOV + local acts = {} + local act + for i = 1, #self.fov.actors_dist do + act = self.fov.actors_dist[i] + if act and self:reactionToward(act) < 0 and not act.dead then + local sx, sy = util.findFreeGrid(act.x, act.y, 1, true, {[engine.Map.ACTOR]=true}) + if sx then acts[#acts+1] = {act, sx, sy} end + end + end + if #acts == 0 then return end + + act = rng.table(acts) + self:move(act[2], act[3], true) + game.level.map:particleEmitter(act[2], act[3], 1, "dark") + self:attackTarget(act[1], DamageType.DARKNESS, eff.dam) -- Attack *and* use energy + end, + deactivate = function(self, eff) + self:removeTemporaryValue("negative_status_effect_immune", eff.sefid) + self:removeTemporaryValue("resists", eff.resid) + end, +} diff --git a/ideas/cunning.ods b/ideas/cunning.ods index 30c13e8c1e16e9be6661be422f3a7410ac345625..6ba8344778c556d8a4434c7f47fb237e878f6a06 100644 Binary files a/ideas/cunning.ods and b/ideas/cunning.ods differ