Newer
Older
--
-- 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
for i = 1, 6 do
local tt = table.clone(t)
tt.short_name = tt.name:upper():gsub("[ ]", "_").."_"..i
tt.display_name = function(self, t)
local data = self:getInscriptionData(t.short_name)
if data.item_name then
local n = tstring{t.name, " ["}
n:merge(data.item_name)
n:add("]")
return n
else
return t.name
end
end
if tt.type[1] == "inscriptions/infusions" then tt.auto_use_check = function(self, t) return not self:hasEffect(self.EFF_INFUSION_COOLDOWN) end
elseif tt.type[1] == "inscriptions/runes" then tt.auto_use_check = function(self, t) return not self:hasEffect(self.EFF_RUNE_COOLDOWN) end
elseif tt.type[1] == "inscriptions/taints" then tt.auto_use_check = function(self, t) return not self:hasEffect(self.EFF_TAINT_COOLDOWN) end
end
tt.auto_use_warning = _t"- will only auto use when no saturation effect exists"
tt.cooldown = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.cooldown
end
tt.old_info = tt.info
tt.info = function(self, t)
local ret = t.old_info(self, t)
local data = self:getInscriptionData(t.short_name)
if data.use_stat and data.use_stat_mod then
ret = ret..("\nIts effects scale with your %s stat."):tformat(self.stats_def[data.use_stat].name)
if not tt.image then
tt.image = "talents/"..(t.short_name or t.name):lower():gsub("[^a-z0-9_]", "_")..".png"
end
tt.no_unlearn_last = true
-----------------------------------------------------------------------
-- Infusions
-----------------------------------------------------------------------
newInscription{
name = "Infusion: Regeneration",
type = {"inscriptions/infusions", 1},
points = 1,
tactical = { HEAL = 2 },
dg
committed
on_pre_use = function(self, t) return not self:hasEffect(self.EFF_REGENERATION) end,
no_break_stealth = true,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_REGENERATION, data.dur, {power=(data.heal + data.inc_stat) / data.dur})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the infusion to heal yourself for %d life over %d turns.]]):tformat(data.heal + data.inc_stat, data.dur)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[heal %d; %d cd]]):tformat(data.heal + data.inc_stat, data.cooldown)
newInscription{
name = "Infusion: Healing",
type = {"inscriptions/infusions", 1},
points = 1,
tactical = { HEAL = 1,
CURE = function(self, t, target)
for eff_id, p in pairs(self.tmp) do
local e = self.tempeffect_def[eff_id]
if e.status == "detrimental" then
if e.subtype.wound then cut = 1 end
if e.subtype.poison then poison = 1 end
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:attr("allow_on_heal", 1)
self:attr("disable_ancestral_life", 1)
self:attr("disable_ancestral_life", -1)
self:attr("allow_on_heal", -1)
DarkGod
committed
self:removeEffectsFilter(self, function(e) return e.subtype.wound end, 1)
self:removeEffectsFilter(self, function(e) return e.subtype.poison end, 1)
self:removeEffectsFilter(self, function(e) return e.subtype.disease end, 1)
self:addParticles(Particles.new("shader_shield_temp", 1, {toback=true , size_factor=1.5, y=-0.3, img="healgreen", life=25}, {type="healing", time_factor=2000, beamsCount=20, noup=2.0}))
self:addParticles(Particles.new("shader_shield_temp", 1, {toback=false, size_factor=1.5, y=-0.3, img="healgreen", life=25}, {type="healing", time_factor=2000, beamsCount=20, noup=1.0}))
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the infusion to instantly heal yourself for %d then cleanse 1 wound, poison, and disease effect.]]):tformat(data.heal + data.inc_stat)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[heal %d; cd %d]]):tformat(data.heal + data.inc_stat, data.cooldown)
tactical = {
CURE = function(self, t, target)
local data = self:getInscriptionData(t.short_name)
return #self:effectsFilter({types=data.what, status="detrimental"})
end
},
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
local target = self
local effs = {}
DarkGod
committed
removed = target:removeEffectsFilter(self, {types=data.what, subtype={["cross tier"] = true}, status="detrimental"})
DarkGod
committed
removed = removed + target:removeEffectsFilter(self, {type=k, status="detrimental"}, 1)
if removed > 0 then
game.logSeen(self, "%s is cured!", self:getName():capitalize())
self:setEffect(self.EFF_PAIN_SUPPRESSION, data.dur, {power=data.power + data.inc_stat})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local what = table.concatNice(table.ts(table.keys(data.what)), ", ", _t" and ")
return ([[Activate the infusion to cure yourself of one random %s effect and reduce all damage taken by %d%% for %d turns.
Also removes cross-tier effects of the affected types for free.]]):tformat(what, data.power+data.inc_stat, data.dur)
local data = self:getInscriptionData(t.short_name)
local what = table.concat(table.ts(table.keys(data.what)), ", ")
return ([[res %d%%; %s; dur %d; cd %d]]):tformat(data.power + data.inc_stat, what, data.dur, data.cooldown)
newInscription{
name = "Infusion: Primal", image = "talents/infusion__wild.png",
type = {"inscriptions/infusions", 1},
points = 1,
no_energy = true,
Chris Davidson
committed
tactical = {DEFEND = 2, CURE = 2},
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_PRIMAL_ATTUNEMENT, data.dur, {power=data.power + data.inc_stat*10, reduce=math.floor(data.reduce + data.inc_stat * 2)})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
Chris Davidson
committed
return ([[Activate the infusion to heal for %d%% of all damage taken (calculated before resistances) and reduce the duration of a random debuff by %d each turn for %d turns.]]):
tformat(data.power+data.inc_stat*10, math.floor((data.reduce or 0) + data.inc_stat * 2), data.dur)
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[affinity %d%%; reduction %d; dur %d; cd %d]]):tformat(data.power + data.inc_stat*10, math.floor((data.reduce or 0) + data.inc_stat * 2), data.dur, data.cooldown )
newInscription{
name = "Infusion: Movement",
type = {"inscriptions/infusions", 1},
points = 1,
Chris Davidson
committed
tactical = { ESCAPE = 1, CLOSEIN = 1 },
on_pre_use = function(self, t) return not self:attr("never_move") end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
game:onTickEnd(function() self:setEffect(self.EFF_WILD_SPEED, 1, {power=data.speed + data.inc_stat}) end)
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the infusion to increase movement speed by %d%% for 1 game turn.
You gain 100%% stun, daze, and pin immunity during the effect.
Any actions other than movement will cancel the effect.
Note: since you will be moving very fast, game turns will pass very slowly.]]):tformat(data.speed + data.inc_stat)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[speed %d%%; cd %d]]):tformat(data.speed + data.inc_stat, data.cooldown)
Chris Davidson
committed
tactical = { DEFEND = 1 },
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
local bonus = 1 + (1 - math.max(0, self.life) / self.max_life)
self:setEffect(self.EFF_HEROISM, math.floor(data.dur * bonus), {die_at=(data.die_at + data.inc_stat * 30) * bonus})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local bonus = 1 + (1 - math.max(0, self.life) / self.max_life)
local bonus1 = (data.die_at + data.inc_stat * 30) * bonus
return ([[Activate the infusion to endure even the most grievous of wounds for %d turns.
While Heroism is active, you will only die when reaching -%d life.
The duration and life will increase by 1%% for every 1%% life you have lost, to a maximum of 100%% at 0 life or less (currently %d life, %d duration)
If your life is below 0 when this effect wears off it will be set to 1.]]):tformat(data.dur, data.die_at + data.inc_stat * 30, bonus1, bonus2)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[die at -%d; dur %d; cd %d]]):tformat(data.die_at + data.inc_stat * 30, data.dur, data.cooldown)
newInscription{
name = "Infusion: Wild Growth",
type = {"inscriptions/infusions", 1},
points = 1,
tactical = { ATTACKAREA = { PHYSICAL = 1, NATURE = 1 }, DEFEND = 1, DISABLE = {pin = 2}},
range = 0,
radius = 5,
direct_hit = true,
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, friendlyfire = false, talent=t}
end,
getDamage = function(self, t) return 10 + self:combatMindpower() * 3.6 end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
local dam = t.getDamage(self, t)
local tg = self:getTalentTarget(t)
self:project(tg, self.x, self.y, function(tx, ty)
DamageType:get(DamageType.ENTANGLE).projector(self, tx, ty, DamageType.ENTANGLE, dam)
end)
self:setEffect(self.EFF_THORNY_SKIN, data.dur, {hard=data.hard or 30, ac=data.armor or 50})
game:playSoundNear(self, "talents/earth")
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local damage = t.getDamage(self, t)
return ([[Causes thick vines to spring from the ground and entangle all targets within %d squares for %d turns, pinning them in place for 5 turns and dealing %0.2f physical damage and %0.2f nature damage.
The vines also grow all around you, increasing your armour by %d and armour hardiness by %d.]]):
tformat(self:getTalentRadius(t), data.dur, damDesc(self, DamageType.PHYSICAL, damage)/3, damDesc(self, DamageType.NATURE, 2*damage)/3, data.armor or 50, data.hard or 30)
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[rad %d; dur %d;]]):tformat(self:getTalentRadius(t), data.dur)
end,
}
-----------------------------------------------------------------------
-- Runes
-----------------------------------------------------------------------
Chris Davidson
committed
local function attack_rune(self, btid)
for tid, lev in pairs(self.talents) do
if tid ~= btid and self.talents_def[tid].is_attack_rune and not self.talents_cd[tid] then
self.talents_cd[tid] = 1
end
end
end
newInscription{
name = "Rune: Teleportation",
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
is_teleport = true,
no_break_stealth = true,
tactical = { ESCAPE = 3 },
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
game.level.map:particleEmitter(self.x, self.y, 1, "teleport")
self:teleportRandom(self.x, self.y, data.range + data.inc_stat, 15)
game.level.map:particleEmitter(self.x, self.y, 1, "teleport")
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the rune to teleport randomly in a range of %d with a minimum range of 15.]]):tformat(data.range + data.inc_stat)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[range %d; cd %d]]):tformat(data.range + data.inc_stat, data.cooldown)
newInscription{
name = "Rune: Shielding",
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
allow_autocast = true,
tactical = { DEFEND = 2 },
on_pre_use = function(self, t)
return not self:hasEffect(self.EFF_DAMAGE_SHIELD)
end,
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_DAMAGE_SHIELD, data.dur, {power=data.power + data.inc_stat})
return true
return ([[Activate the rune to create a protective shield absorbing at most %d damage for %d turns.]]):tformat(self:getShieldAmount(data.power + data.inc_stat), self:getShieldDuration(data.dur))
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[absorb %d; dur %d; cd %d]]):tformat(self:getShieldAmount(data.power + data.inc_stat), self:getShieldDuration(data.dur), data.cooldown)
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
allow_autocast = true,
no_energy = true,
tactical = { DEFEND = 2 },
getPower = function(self, t)
local data = self:getInscriptionData(t.short_name)
if data.power and data.inc_stat then
return data.power + data.inc_stat
else
return 100+5*self:getMag()
end
end,
getDuration = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.dur or 5
end,
on_pre_use = function(self, t)
return not self:hasEffect(self.EFF_DAMAGE_SHIELD)
end,
action = function(self, t)
local power = t.getPower(self, t)
local dur = t.getDuration(self, t)
self:setEffect(self.EFF_DAMAGE_SHIELD, dur, {power=power, reflect=100, image="reflectionshield_17", shield_intensity=0.6})
return true
end,
info = function(self, t)
local power = t.getPower(self, t)
local dur = t.getDuration(self, t)
return ([[Activate the rune to create a protective shield absorbing and reflecting at most %d damage for %d turns.]]):tformat(self:getShieldAmount(power), self:getShieldDuration(dur))
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local power = t.getPower(self, t)
local dur = t.getDuration(self, t)
return ([[absorb and reflect %d; dur %d; cd %d]]):tformat(self:getShieldAmount(power), self:getShieldDuration(dur), data.cd)
is_attack_rune = true,
no_energy = true,
Chris Davidson
committed
tactical = { ATTACK = { COLD = 1 }, DISABLE = { stun = 1 } },
Chris Davidson
committed
range = 0,
dg
committed
target = function(self, t)
return {type="cone", cone_angle=25, radius = self:getTalentRadius(t), range=self:getTalentRange(t), talent=t, display={particle="bolt_ice", trail="icetrail"}}
dg
committed
end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
dg
committed
local tg = self:getTalentTarget(t)
local x, y = self:getTarget(tg)
if not x or not y then return nil end
Chris Davidson
committed
local damage = data.power + data.inc_stat
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if not target or target == self then return end
DamageType:get(DamageType.COLD).projector(self, tx, ty, DamageType.COLD, damage)
Chris Davidson
committed
target:setEffect(target.EFF_WET, 5, {})
if target:canBe("stun") then
Chris Davidson
committed
target:setEffect(target.EFF_FROZEN, data.dur, {hp=damage*2})
end
end, data.power + data.inc_stat, {type="freeze"})
attack_rune(self, t.id)
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the rune to direct a cone of chilling stormwind doing %0.2f cold damage.
The storm will soak enemies hit reducing their resistance to stuns by 50%% then attempt to freeze them for %d turns.
Chris Davidson
committed
These effects can be resisted but not saved against.]]):
tformat(damDesc(self, DamageType.COLD, data.power + data.inc_stat), data.dur)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[damage %d; dur %d; cd %d]]):tformat(damDesc(self, DamageType.COLD, data.power + data.inc_stat), data.dur, data.cooldown)
}
newInscription{
name = "Rune: Acid Wave",
type = {"inscriptions/runes", 1},
points = 1,
is_attack_rune = true,
no_energy = true,
tactical = {
ATTACKAREA = { ACID = 1 },
Chris Davidson
committed
DISABLE = { disarm = 1 }
requires_target = true,
direct_hit = true,
Chris Davidson
committed
radius = 6,
range = 0,
target = function(self, t)
Chris Davidson
committed
return {type="cone", radius=self:getTalentRadius(t), range=self:getTalentRange(t), selffire=false, cone_angle=25, talent=t}
end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
local tg = self:getTalentTarget(t)
local x, y = self:getTarget(tg)
if not x or not y then return nil end
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if not target or target == self then return end
if target:canBe("disarm") then
Chris Davidson
committed
target:setEffect(target.EFF_DISARMED, data.dur, {})
end
DamageType:get(DamageType.ACID).projector(self, tx, ty, DamageType.ACID, data.power + data.inc_stat)
end)
game.level.map:particleEmitter(self.x, self.y, tg.radius, "breath_acid", {radius=tg.radius, tx=x-self.x, ty=y-self.y})
attack_rune(self, t.id)
local data = self:getInscriptionData(t.short_name)
Chris Davidson
committed
return ([[Activate the rune to unleash a cone dealing %0.2f acid damage.
The corrosive acid will also disarm enemies struck for %d turns.
This effect can be resisted but not saved against.]]):
tformat(damDesc(self, DamageType.ACID, data.power + data.inc_stat), data.dur or 3)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[damage %d; dur %d; cd %d]]):tformat(damDesc(self, DamageType.ACID, data.power + data.inc_stat), data.dur or 3, data.cooldown)
Chris Davidson
committed
-- Incredibly specific to one resource top, a generalization applying to the other arcane resources is worth considering
-- This serves as one of the primary counters to mana drain effects since it lets you recover from hitting 0
Chris Davidson
committed
name = "Rune: Manasurge",
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
no_break_stealth = true,
Chris Davidson
committed
tactical = { MANA = 1 },
on_pre_use = function(self, t)
return self:knowTalent(self.T_MANA_POOL) and not self:hasEffect(self.EFF_MANASURGE)
on_learn = function(self, t)
self.mana_regen_on_rest = (self.mana_regen_on_rest or 0) + 0.5
end,
on_unlearn = function(self, t)
self.mana_regen_on_rest = (self.mana_regen_on_rest or 0) - 0.5
end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
dg
committed
self:incMana((data.mana + data.inc_stat) / 20)
self:setEffect(self.EFF_MANASURGE, data.dur, {power=self.mana_regen * (data.mana + data.inc_stat) / 100})
else
if self.mana_regen < 0 then
game.logPlayer(self, "Your negative mana regeneration rate is unaffected by the rune.")
else
game.logPlayer(self, "Your nonexistant mana regeneration rate is unaffected by the rune.")
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local total = (data.mana + data.inc_stat) / 100 * (self.mana_regen or 0) * 10
return ([[Activate the rune to unleash a manasurge upon yourself, increasing mana regeneration by %d%% for %d turns (%d total) and instantly restoring %d mana.
Also when resting your mana will regenerate at 0.5 per turn.]]):tformat(data.mana + data.inc_stat, data.dur, total, (data.mana + data.inc_stat) / 20)
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[regen %d%% over %d turns; mana %d; cd %d]]):tformat(data.mana + data.inc_stat, data.dur, (data.mana + data.inc_stat) / 20, data.cooldown)
newInscription{
name = "Rune of the Rift",
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
tactical = { DISABLE = 2, ATTACK = { TEMPORAL = 1 } },
direct_hit = true,
reflectable = true,
requires_target = true,
dg
committed
target = function(self, t)
return {type="hit", range=self:getTalentRange(t), talent=t}
end,
getDamage = function(self, t) return 150 + self:getWil() * 4 end,
getDuration = function(self, t) return 4 end,
action = function(self, t)
dg
committed
local tg = self:getTalentTarget(t)
local x, y = self:getTarget(tg)
if not x or not y then return nil end
dg
committed
local _ _, x, y = self:canProject(tg, x, y)
local target = game.level.map(x, y, Map.ACTOR)
if not target then return end
if target:attr("timetravel_immune") then
game.logSeen(target, "%s is immune!", target:getName():capitalize())
local hit = self:checkHit(self:combatSpellpower(), target:combatSpellResist() + (target:attr("continuum_destabilization") or 0))
if not hit then game.logSeen(target, "%s resists!", target:getName():capitalize()) return true end
self:project(tg, x, y, DamageType.TEMPORAL, self:spellCrit(t.getDamage(self, t)))
game.level.map:particleEmitter(x, y, 1, "temporal_thrust")
game:playSoundNear(self, "talents/arcane")
if target.dead or target.player then return true end
dg
committed
target:setEffect(target.EFF_CONTINUUM_DESTABILIZATION, 100, {power=self:combatSpellpower(0.3)})
-- Placeholder for the actor
local oe = game.level.map(x, y, Map.TERRAIN+1)
if (oe and oe:attr("temporary")) or game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then game.logPlayer(self, "Something has prevented the timetravel.") return true end
dg
committed
local e = mod.class.Object.new{
old_feat = oe, type = "temporal", subtype = "instability",
display = '&', color=colors.LIGHT_BLUE,
temporary = t.getDuration(self, t),
canAct = false,
target = target,
act = function(self)
self:useEnergy()
self.temporary = self.temporary - 1
-- return the rifted actor
if self.temporary <= 0 then
-- remove ourselves
if self.old_feat then game.level.map(self.target.x, self.target.y, engine.Map.TERRAIN+1, self.old_feat)
else game.level.map:remove(self.target.x, self.target.y, engine.Map.TERRAIN+1) end
game.nicer_tiles:updateAround(game.level, self.target.x, self.target.y)
game.level:removeEntity(self)
game.level.map:removeParticleEmitter(self.particles)
-- return the actor and reset their values
local mx, my = util.findFreeGrid(self.target.x, self.target.y, 20, true, {[engine.Map.ACTOR]=true})
game.zone:addEntity(game.level, self.target, "actor", mx, my)
end
end,
dg
committed
summoner_gain_exp = true, summoner = self,
game.logSeen(target, "%s has moved forward in time!", target:getName():capitalize())
-- add the time skip object to the map
local particle = Particles.new("wormhole", 1, {image="shockbolt/terrain/temporal_instability_yellow", speed=1})
particle.zdepth = 6
e.particles = game.level.map:addParticleEmitter(particle, x, y)
dg
committed
game.level:addEntity(e)
dg
committed
game.level.map:updateMap(x, y)
return true
end,
info = function(self, t)
local damage = t.getDamage(self, t)
local duration = t.getDuration(self, t)
return ([[Inflicts %0.2f temporal damage. If your target survives, it will be sent %d turns into the future.
It will also lower your paradox by 25 (if you have any).
Note that messing with the spacetime continuum may have unforeseen consequences.]]):tformat(damDesc(self, DamageType.TEMPORAL, damage), duration)
end,
short_info = function(self, t)
return ("%0.2f temporal damage, removed from time %d turns"):tformat(t.getDamage(self, t), t.getDuration(self, t))
end,
}
Chris Davidson
committed
-- New name for the merged Phase Doors
Chris Davidson
committed
newInscription{
name = "Rune: Blink",
image = "talents/rune__controlled_phase_door.png",
Chris Davidson
committed
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
is_teleport = true,
Chris Davidson
committed
tactical = { ESCAPE = 1, CLOSEIN = 1 },
range = function(self, t)
local data = self:getInscriptionData(t.short_name)
Chris Davidson
committed
end,
target = function(self, t) return {type="hit", nolock=true, pass_terrain=false, nowarning=true, range=self:getTalentRange(t),
Chris Davidson
committed
grid_params = {want_range = (not self.ai_target.actor or self.ai_state.tactic == "escape") and 6 or 1 } } end,
getDur = function(self, t) return 3 end,
Chris Davidson
committed
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
Chris Davidson
committed
local tg = self:getTalentTarget(t)
Chris Davidson
committed
local x, y = self:getTarget(tg)
if not x then return end
if not self:hasLOS(x, y) or game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move") then return end
Chris Davidson
committed
local _ _, x, y = self:canProject(tg, x, y)
Chris Davidson
committed
game.level.map:particleEmitter(self.x, self.y, 1, "teleport")
self:teleportRandom(x, y, rad)
game.level.map:particleEmitter(self.x, self.y, 1, "teleport")
self:setEffect(self.EFF_OUT_OF_PHASE, data.dur or 3, {
defense = data.power + data.inc_stat * 3,
resists = data.power + data.inc_stat * 3,
effect_reduction = data.power + data.inc_stat * 3,
Chris Davidson
committed
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local power = data.power + data.inc_stat * 3
return ([[Activate the rune to teleport up to %d spaces within line of sight. Afterwards you stay out of phase for %d turns. In this state all new negative status effects duration is reduced by %d%%, your defense is increased by %d and all your resistances by %d%%.]]):
tformat(t.range(self, t), t.getDur(self, t), power, power, power)
Chris Davidson
committed
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local power = data.power + data.inc_stat * 3
return ([[range %d; phase %d; cd %d]]):tformat(self:getTalentRange(t), power, data.cooldown )
Chris Davidson
committed
end,
}
-- Invisibility updated to have combat value and more escape potential
Chris Davidson
committed
newInscription{
name = "Rune: Ethereal",
image = "talents/rune__invisibility.png",
Chris Davidson
committed
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
Chris Davidson
committed
tactical = { ESCAPE = 1 },
Chris Davidson
committed
getDur = function(self, t) return 5 end,
Chris Davidson
committed
local data = self:getInscriptionData(t.short_name)
return data.resist + data.inc_stat * 2
end,
getReduction = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.reduction
Chris Davidson
committed
end,
getPower = function(self, t)
local data = self:getInscriptionData(t.short_name)

Ryan Yappert
committed
return math.ceil(data.power + data.inc_stat * 2)
end,
getMove = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.move + data.inc_stat
Chris Davidson
committed
end,
Chris Davidson
committed
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_ETHEREAL, t.getDur(self, t), {power=t.getPower(self, t), reduction=t.getReduction(self, t), resist=t.getResistance(self, t), move=t.getMove(self, t)})
Chris Davidson
committed
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the rune to become ethereal for %d turns.
While ethereal all damage you deal is reduced by %d%%, you gain %d%% all resistance, you move %d%% faster, and you are invisible (power %d).]]):
tformat(t.getDur(self, t),t.getReduction(self, t) * 100, t.getResistance(self, t), t.getMove(self, t), t.getPower(self, t))
Chris Davidson
committed
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[power %d; resist %d%%; move %d%%; dur %d; cd %d]]):tformat(t.getPower(self, t), t.getResistance(self, t), t.getMove(self, t), t.getDur(self, t), data.cooldown)
Chris Davidson
committed
end,
}
-- Lightning Rune replacement, concept partially kept
-- Numbers on this must be done carefully
newInscription{
name = "Rune: Stormshield",
image = "talents/rune__lightning.png",
Chris Davidson
committed
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
Chris Davidson
committed
no_energy = true,
tactical = { DEFEND = 1 },
Chris Davidson
committed
getDur = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.dur
end,
getThreshold = function(self, t)
local data = self:getInscriptionData(t.short_name)
return math.round(data.threshold)
Chris Davidson
committed
end,
getBlocks = function(self, t)
local data = self:getInscriptionData(t.short_name)
return math.round(data.blocks + data.inc_stat)
Chris Davidson
committed
end,
Chris Davidson
committed
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_STORMSHIELD, t.getDur(self, t), {threshold=t.getThreshold(self, t), blocks=t.getBlocks(self, t)})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[Activate the rune to summon a protective storm around you for %d turns.
While active the storm will completely block all damage over %d up to %d times.]])
:tformat(t.getDur(self, t), t.getThreshold(self, t), t.getBlocks(self, t) )
Chris Davidson
committed
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[threshold %d; blocks %d; dur %d; cd %d]]):tformat(t.getThreshold(self, t), t.getBlocks(self, t), t.getDur(self, t), data.cooldown )
Chris Davidson
committed
end,
}
-- Fixedart generated with a random ward set
newInscription{
name = "Rune: Prismatic",
image = "talents/ward.png", -- re-used icon
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
no_break_stealth = true,
getDur = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.dur
end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
self:setEffect(self.EFF_PRISMATIC_SHIELD, t.getDur(self, t), {wards = table.clone(data.wards)})
return true
end,
info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local str = ""
for k,v in pairs(data.wards) do
return ([[Activate the rune to create a shield for %d turns blocking several instances of damage of the following types:%s]]) -- color me
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
local str = table.concat(table.ts(table.lower(table.keys(data.wards))), ", ")
return ([[%d turns; %s]]):tformat(t.getDur(self, t), str:lower() )
newInscription{
name = "Rune: Mirror Image",
type = {"inscriptions/runes", 1},
image = "talents/phase_shift.png", -- re-used icon
no_break_stealth = true,
getDur = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.dur
end,
getInheritance = function(self, t)
local data = self:getInscriptionData(t.short_name)
end,
getInheritedResist = function(self, t)
local res = {}
for k,v in pairs(self.resists) do
res[k] = (t.getInheritance(self, t) * (self.resists[k]) or 0)
end
return res
end,
action = function(self, t)
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
if not self:canBe("summon") then game.logPlayer(self, "You cannot summon; you are suppressed!") return end
-- Find all actors in radius 10 and add them to a table
local tg = {type="ball", radius=self.sight}
local grids = self:project(tg, self.x, self.y, function() end)
local tgts = {}
for x, ys in pairs(grids) do for y, _ in pairs(ys) do
local target = game.level.map(x, y, Map.ACTOR)
if target and self:reactionToward(target) < 0 then tgts[#tgts+1] = target end
end end
for _ = 1,3 do
local target = rng.tableRemove(tgts)
if target then
local tx, ty = util.findFreeGrid(target.x, target.y, 10, true, {[Map.ACTOR]=true})
if tx then
local Talents = require "engine.interface.ActorTalents"
local NPC = require "mod.class.NPC"
local caster = self
local image = NPC.new{
name = _t"Mirror Image",
type = "image", subtype = "image",
ai = "summoned", ai_real = nil, ai_state = { talent_in=1, }, ai_target = {actor=nil},
desc = _t"A blurred image.",
image = caster.image,
add_mos = table.clone(caster.add_mos, true),
shader = "shadow_simulacrum", shader_args = { color = {0.0, 0.4, 0.8}, base = 0.6, time_factor = 1500 },
exp_worth=0,
max_life = caster.max_life,
life = caster.max_life, -- We don't want to make this only useful before you take damage
combat_armor_hardiness = caster:combatArmorHardiness(),
combat_def = caster:combatDefense(),
combat_armor = caster:combatArmor(),
size_category = caster.size_category,
resists = t.getInheritedResist(self, t),
rank = 1,
life_rating = 0,
cant_be_moved = 1,
never_move = 1,
never_anger = true,
resolvers.talents{
[Talents.T_TAUNT]=1, -- Add the talent so the player can see it even though we cast it manually
},
on_act = function(self) -- avoid any interaction with .. uh, anything
self:forceUseTalent(self.T_TAUNT, {ignore_cd=true, no_talent_fail = true})
end,
faction = caster.faction,
summoner = caster,
summon_time=t.getDur(self, t),
no_breath = 1,
remove_from_party_on_death = true,
}
image:resolve()
game.zone:addEntity(game.level, image, "actor", tx, ty)
if game.party:hasMember(self) then
game.party:addMember(image, {
control=false,
type="summon",
Chris Davidson
committed
temporary_level = true,
orders = {},
image:forceUseTalent(image.T_TAUNT, {ignore_cd=true, no_talent_fail = true})
return true
end,
info = function(self, t)
return ([[Activate the rune to create up to 3 images of yourself that taunt nearby enemies each turn and immediately after being summoned.
Only one image can be created per enemy in radius 10 with the first being created near the closest enemy.
Images inherit all of your life, resistance, armor, defense, and armor hardiness.]])
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[dur %d; cd %d]]):tformat(t.getDur(self, t), data.cooldown)
end
}
-- Counter to mass multitype debuff spam
-- This is the counterpart to Wild but scales differently, acknowledging the fact that triple type cleanse becomes better as the game progresses
newInscription{
name = "Rune: Shatter Afflictions",
image = "talents/warp_mine_away.png", -- re-used icon
type = {"inscriptions/runes", 1},
points = 1,
Chris Davidson
committed
tactical = { CURE = function(self, t, target)
local types = 0
types = types + #self:effectsFilter({status="detrimental", type="physical"}, 1)
types = types + #self:effectsFilter({status="detrimental", type="magical"}, 1)
types = types + #self:effectsFilter({status="detrimental", type="mental"}, 1)
return types
end
},
is_spell = true,
no_energy = true,
getShield = function(self, t)
local data = self:getInscriptionData(t.short_name)
return data.shield + data.inc_stat
end,
getDuration = function(self, t)
return 3
end,
on_pre_use = function(self, t)
if next(self:effectsFilter({type="physical", status="detrimental"}, 1)) then return true end
if next(self:effectsFilter({type="magical", status="detrimental"}, 1)) then return true end
if next(self:effectsFilter({type="mental", status="detrimental"}, 1)) then return true end
if next(self:effectsFilter({subtype={["cross tier"] = true}, status="detrimental"}, 3)) then return true end
return false
end,
action = function(self, t)
local data = self:getInscriptionData(t.short_name)
DarkGod
committed
local crosstiers = self:removeEffectsFilter(self, {subtype={["cross tier"] = true}, status="detrimental"}, 3)
DarkGod
committed
cleansed = cleansed + self:removeEffectsFilter(self, {type="physical", status="detrimental"}, 1)
cleansed = cleansed + self:removeEffectsFilter(self, {type="magical", status="detrimental"}, 1)
cleansed = cleansed + self:removeEffectsFilter(self, {type="mental", status="detrimental"}, 1)
if crosstiers == 0 and cleansed == 0 then return nil end
if cleansed > 0 then
self:setEffect(self.EFF_DAMAGE_SHIELD, t.getDuration(self, t), {power=(data.shield + data.inc_stat) * cleansed})
else
game:onTickEnd(function() self:alterTalentCoolingdown(t.id, -math.floor((self.talents_cd[t.id] or 0) * 0.75)) end)
end
return true
end,
info = function(self, t)
return ([[Activate the rune to instantly dissipate the energy of your ailments, cleansing all cross tier effects and 1 physical, mental, and magical effect.
You use the dissipated energy to create a shield lasting %d turns and blocking %d damage per debuff cleansed (not counting cross-tier ones).
If there were only cross-tier effects to cleanse, no shield is created and the rune goes on a 75%% reduced cooldown.]])
:tformat(self:getShieldDuration(t.getDuration(self, t)), self:getShieldAmount(t.getShield(self, t)))
end,
short_info = function(self, t)
local data = self:getInscriptionData(t.short_name)
return ([[absorb %d; cd %d]]):tformat(self:getShieldAmount(t.getShield(self, t)), data.cooldown)
end,
}
newInscription{
name = "Rune: Dissipation",
image = "talents/disperse_magic.png", -- re-used icon
type = {"inscriptions/runes", 1},
points = 1,
is_spell = true,
range = 10,
direct_hit = true,
target = function(self, t) return {default_target=self, type="hit", nowarning=true, range=self:getTalentRange(t)} end,
tactical = {
DISABLE = function(self, t, aitarget)
local nb = 0
for tid, act in pairs(aitarget.sustain_talents) do
if act then
local talent = aitarget:getTalentFromId(tid)
if talent.is_spell then nb = nb + 1 end
end
end