High speed (attack or global speed) actors may get to use actions while under the effect of the Shadow Veil, such as casting spells. This is caused when the automatic attacks do not use enough energy to force the actor into the next turn. I have fixed this by setting the self.never_act flag when appropriate and by giving the actor enough attacks to use up the allotted energy each turn.
Shadow Veiled actors will sometimes attack targets that are out of FOV. This may happen if the self.fov.actor_dist table is not updated between blinks and can result in blinking across the map after targets that have teleported, for example. I have added a self:doFOV command to update the table, and have included a range limit for the blinks (of from 5 to 8 tiles, depending on talent level) that will prevent going after targets that are not "nearby."
When an actor with Shadow Veil casts Ambuscade, both the inert actor and the shadow will be Shadow Veiled and can both attack if targets come into view. This can be quite overpowered, particularly for random bosses. I have changed the talent code for Ambuscade to clear the Shadow Veil effect from the caster, so that only the shadow has it.
Only 10 talent levels were removed from disallowed talents for the Ambuscade shadow. I have changed the code to remove all talent levels from the shadow.
Special thanks for tiger_eye for helping track down the fov calls.
Here is the patch:
Code: Select all
Index: game/modules/tome/data/talents/cunning/ambush.lua
===================================================================
--- game/modules/tome/data/talents/cunning/ambush.lua (revision 6365)
+++ game/modules/tome/data/talents/cunning/ambush.lua (working copy)
@@ -152,12 +152,13 @@
m.exp_worth = 0
m.no_inventory_access = true
m.stealth = t.getStealthPower(self, t)
- for i = 1, 10 do
- m:unlearnTalent(m.T_AMBUSCADE)
- m:unlearnTalent(m.T_PROJECTION) -- no recurssive projections
- m:unlearnTalent(m.T_STEALTH)
- m:unlearnTalent(m.T_HIDE_IN_PLAIN_SIGHT)
- end
+
+ m:unlearnTalent(m.T_AMBUSCADE,m:getTalentLevelRaw(m.T_AMBUSCADE))
+ m:unlearnTalent(m.T_PROJECTION,m:getTalentLevelRaw(m.T_PROJECTION)) -- no recurssive projections
+ m:unlearnTalent(m.T_STEALTH,m:getTalentLevelRaw(m.T_STEALTH))
+ m:unlearnTalent(m.T_HIDE_IN_PLAIN_SIGHT,m:getTalentLevelRaw(m.T_HIDE_IN_PLAIN_SIGHT))
+
+ self:removeEffect(self.EFF_SHADOW_VEIL) -- Remove shadow veil from creator
m.remove_from_party_on_death = true
m.resists[DamageType.LIGHT] = -100
m.resists[DamageType.DARKNESS] = 130
@@ -213,8 +214,9 @@
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,
+ getBlinkRange = function(self, t) return math.ceil(3.3 + 1.7*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)})
+ self:setEffect(self.EFF_SHADOW_VEIL, t.getDuration(self, t), {res=t.getDamageRes(self, t), dam=t.getDamage(self, t), range=t.getBlinkRange(self, t)})
return true
end,
info = function(self, t)
@@ -222,9 +224,10 @@
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 and gain %d%% all damage reduction. Each turn, you blink to a nearby foe, hitting it for %d%% darkness weapon damage.
+ While veiled, you become immune to status effects and gain %d%% all damage reduction. Each turn, you blink to a nearby foe (within range %d), hitting it for %d%% darkness weapon damage.
While this goes on, you cannot be stopped unless you are killed, and you cannot control your character.]]):
- format(duration, res, 100 * damage)
+ format(duration, res, t.getBlinkRange(self, t) ,100 * damage)
end,
}
+
Index: game/modules/tome/data/timed_effects/other.lua
===================================================================
--- game/modules/tome/data/timed_effects/other.lua (revision 6365)
+++ game/modules/tome/data/timed_effects/other.lua (working copy)
@@ -573,11 +573,11 @@
newEffect{
name = "SHADOW_VEIL", image = "talents/shadow_veil.png",
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, and gain %d%% all damage reduction. Each turn you blink to a nearby foe, hitting it for %d%% darkness weapon damage. While this goes on you cannot be stopped unless you are killed, and you cannot control your character."):format(eff.res, eff.dam * 100) end,
+ 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, and gain %d%% all damage reduction. Each turn you blink to a nearby foe within range %d, hitting it for %d%% darkness weapon damage. While this goes on you cannot be stopped unless you are killed, and you cannot control your character."):format(eff.res, eff.range, eff.dam * 100) end,
type = "other",
subtype = { darkness=true },
status = "beneficial",
- parameters = { res=10, dam=1.5 },
+ parameters = { res=10, dam=1.5, range=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)
@@ -585,26 +585,33 @@
eff.resid = self:addTemporaryValue("resists", {all=eff.res})
end,
on_timeout = function(self, eff)
+ local maxdist = self:callTalent(self.T_SHADOW_VEIL,"getBlinkRange")
+ self.never_act = true
+ repeat
-- 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
+ local acts = {}
+ local act
+
+ self:doFOV() -- update actors seen
+ 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 and self:isNear(act.x,act.y,maxdist) 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
- end
- if #acts == 0 then return end
+ if #acts == 0 then self.never_act = nil 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
+ 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
+ until self.energy.value < 0 --I5 keep blinking and attacking until out of energy (since on_timeout is only once per turn)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("negative_status_effect_immune", eff.sefid)
self:removeTemporaryValue("resists", eff.resid)
+ self.never_act = nil
end,
}