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
--- general tactical function accounting for Hymn Acolyte and Hymn Incantor
-- uses t.base_tactical and t.adept_deac_tactical
local hymn_tactical = function(self, t, aitarget)
local bt_type, tacs = type(t.base_tactical)
tacs = t.base_tactical(self, t, aitarget)
elseif bt_type == "table" then
tacs = table.clone(t.base_tactical)
end
tacs = tacs or {}
local lvl = self:getTalentLevel(self.T_HYMN_INCANTOR)
if lvl > 0 then
tacs.buff = (tacs.buff or 0) + 2*lvl/(lvl + 5)
end
lvl = self:getTalentLevel(self.T_HYMN_ADEPT)
if lvl > 0 and self:isTalentActive(t.id) then
if t.adept_deac_tactical then
table.mergeAdd(tacs, t.adept_deac_tactical)
end
tacs.special = (tacs.special or 0) + 0.01 -- forces some "inertia" in toggling hymns
end
return tacs
end
name = "Hymn of Shadows",
type = {"celestial/hymns-hymns", 1},
mode = "sustained",
require = divi_req1,
points = 5,
cooldown = 12,
sustain_negative = 20,
no_energy = true,
tactical = hymn_tactical,
base_tactical = { buff=1, defend=0.5, escape=1 , closein=1},
adept_deac_tactical = {escape = -0.5, closein = -0.5}, -- adept tactic adjustments when deactivating (negated)
moveSpeed = function(self, t) return self:combatTalentSpellDamage(t, 20, 50) end,
castSpeed = function(self, t) return self:combatTalentSpellDamage(t, 7, 20) end,
sustain_slots = 'celestial_hymn',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = {}
self:talentTemporaryValue(ret, "movement_speed", t.moveSpeed(self, t)/100)
self:talentTemporaryValue(ret, "combat_spellspeed", t.castSpeed(self, t)/100)
ret.particle = self:addParticles(Particles.new("darkness_shield", 1))
if self:knowTalent(self.T_HYMN_INCANTOR) then
local t2 = self:getTalentFromId(self.T_HYMN_INCANTOR)
self:talentTemporaryValue(ret, "on_melee_hit", {[DamageType.DARKNESS]=t2.getDamageOnMeleeHit(self, t2)})
self:talentTemporaryValue(ret, "inc_damage", {[DamageType.DARKNESS] = t2.getDarkDamageIncrease(self, t2)})
end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
self:talentTemporaryValue(ret, "infravision", t2.getBonusInfravision(self, t2))
end
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
if self.turn_procs.resetting_talents then return true end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
game:onTickEnd(function() self:setEffect(self.EFF_WILD_SPEED, 1, {power=t2.getSpeed(self, t2), no_talents=0}) end)
return true
end,
info = function(self, t)
return ([[Chant the glory of the Moons, gaining the agility of shadows.
This increases your movement speed by %d%% and your spell speed by %d%%.
You may only have one Hymn active at once.
The effects will increase with your Spellpower.]]):
name = "Hymn of Detection",
type = {"celestial/hymns-hymns", 1},
mode = "sustained",
cooldown = 12,
sustain_negative = 20,
no_energy = true,
tactical = hymn_tactical,
adept_deac_tactical = {escape = -0.5}, -- adept tactic adjustments when deactivating (negated)
base_tactical = function(self, t, aitarget) -- buff tactic up to +3 depending on how much the talent helps to see a stealthed/invisible target or a target that is out of sense range
local buff, max_buff = 0, 4
if not aitarget or not self.aiSeeTargetPos then -- out of combat, stay vigilant for enemies
buff = max_buff
else
if self:attr("blind") or self:attr("esp_all") then return end -- target automatically seen or unseen
local ax, ay = self:aiSeeTargetPos(aitarget)
local dist = core.fov.distance(self.x, self.y, ax, ay)
if dist <= self.sight then -- target within sight range
local is_active = self:isTalentActive(t.id)
local see, see_chance = self:canSeeNoCache(aitarget)
if see_chance and see_chance < 100 then -- detecting hidden is useful (rules out esp, etc.)
local tgt_inv = aitarget:attr("invisible")
if tgt_inv then -- buff value increases depending on relative invisibility detection
local base_det = self:combatSeeInvisible() - (is_active and self.compute_vals[is_active.invis] or 0)
if base_det <= 0 then return {buff = max_buff} end -- cannot see at all with no detection ability
local need_det = tgt_inv - base_det
buff = buff + math.max(0, max_buff*(0.5 + need_det/(math.abs(need_det) + 20))) -- adds 0 (need_det = -20) to max_buff (need_det = +20)
end
local tgt_stealth = aitarget:attr("stealth")
if tgt_stealth then -- buff value increases depending on relative stealth detection
tgt_stealth = tgt_stealth + (aitarget:attr("inc_stealth") or 0)
local base_det = is_active and is_active.base_see_stealth or self:combatSeeStealth()
local need_det = tgt_stealth - base_det
buff = buff + math.max(0, max_buff*(0.5 + need_det/(math.abs(need_det) + 20))) -- adds 0 (need_det = -20) to max_buff (need_det = +20)
end
end
end
end
buff=math.min(buff, max_buff)
return {buff=buff + 0.25, special = buff/2}
sustain_slots = 'celestial_hymn',
getSeeInvisible = function(self, t) return self:combatTalentSpellDamage(t, 2, 25) end,
getSeeStealth = function(self, t) return self:combatTalentSpellDamage(t, 2, 25) end,
critPower = function(self, t) return self:combatTalentSpellDamage(t, 10, 50) end,
callbackOnActBase = function(self, t)
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
-- base_see_stealth = self:combatSeeStealth(),
local ret = {}
self:talentTemporaryValue(ret, "see_invisible", t.getSeeInvisible(self, t))
self:talentTemporaryValue(ret, "see_stealth", t.getSeeStealth(self, t))
self:talentTemporaryValue(ret, "blindfight", 1)
self:talentTemporaryValue(ret, "combat_critical_power", t.critPower(self, t))
ret.particle = self:addParticles(Particles.new("darkness_shield", 1))
if self:knowTalent(self.T_HYMN_INCANTOR) then
local t2 = self:getTalentFromId(self.T_HYMN_INCANTOR)
self:talentTemporaryValue(ret, "on_melee_hit", {[DamageType.DARKNESS]=t2.getDamageOnMeleeHit(self, t2)})
self:talentTemporaryValue(ret, "inc_damage", {[DamageType.DARKNESS] = t2.getDarkDamageIncrease(self, t2)})
end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
self:talentTemporaryValue(ret, "infravision", t2.getBonusInfravision(self, t2))
end
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
if self.turn_procs.resetting_talents then return true end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
self:setEffect(self.EFF_INVISIBILITY, t2.invisDur(self, t2), {power=t2.invisPower(self, t2), penalty=0.4})
end
return true
end,
info = function(self, t)
local invis = t.getSeeInvisible(self, t)
local stealth = t.getSeeStealth(self, t)
return ([[Chant the glory of the Moons, granting you stealth detection (+%d power), and invisibility detection (+%d power).
You may also attack creatures you cannot see without penalty and your critical hits do %d%% more damage.
You may only have one Hymn active at once.
The stealth and invisibility detection will increase with your Spellpower.]]):
name = "Hymn of Perseverance",
type = {"celestial/hymns-hymns",1},
mode = "sustained",
cooldown = 12,
sustain_negative = 20,
no_energy = true,
tactical = hymn_tactical,
base_tactical = {defend=1}, -- could check base immunities here
adept_deac_tactical = {defend = -0.5}, -- adept tactic adjustments when deactivating (negated)
range = 10,
getImmunities = function(self, t) return self:combatTalentLimit(t, 1, 0.2, 0.45) end, -- Limit < 100%
sustain_slots = 'celestial_hymn',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = {}
self:talentTemporaryValue(ret, "stun_immune", t.getImmunities(self, t))
self:talentTemporaryValue(ret, "confusion_immune", t.getImmunities(self, t))
self:talentTemporaryValue(ret, "blind_immune", t.getImmunities(self, t))
ret.particle = self:addParticles(Particles.new("darkness_shield", 1))
if self:knowTalent(self.T_HYMN_INCANTOR) then
local t2 = self:getTalentFromId(self.T_HYMN_INCANTOR)
self:talentTemporaryValue(ret, "on_melee_hit", {[DamageType.DARKNESS]=t2.getDamageOnMeleeHit(self, t2)})
self:talentTemporaryValue(ret, "inc_damage", {[DamageType.DARKNESS] = t2.getDarkDamageIncrease(self, t2)})
end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
self:talentTemporaryValue(ret, "infravision", t2.getBonusInfravision(self, t2))
end
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
if self.turn_procs.resetting_talents then return true end
if self:knowTalent(self.T_HYMN_ADEPT) then
local t2 = self:getTalentFromId(self.T_HYMN_ADEPT)
self:setEffect(self.EFF_DAMAGE_SHIELD, t2.shieldDur(self, t2), {power=t2.shieldPower(self, t2)})
end
return true
end,
info = function(self, t)
return ([[Chant the glory of the Moons, granting you %d%% stun, blindness and confusion resistance.
You may only have one Hymn active at once.]]):
-- Depreciated, but retained for compatability.
name = "Hymn of Moonlight",
type = {"celestial/hymns-hymns",1},
mode = "sustained",
require = divi_req4,
points = 5,
cooldown = 12,
sustain_negative = 20,
no_energy = true,
tactical = { ATTACKAREA = { DARKNESS = 1 },
NEGATIVE = function(self, t, target)
return t.getNegativeDrain(self, t)/15 -- negated for foes
end
},
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 7, 80) end,
getTargetCount = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
getNegativeDrain = function(self, t) return self:combatTalentLimit(t, 0, 7, 3) end, -- Limit > 0, no regen at high levels
DarkGod
committed
return {type="ball", friendlyfire=false, friendlyblock=false, radius=self:getTalentRange(t), range=0, talent=t}
callbackOnActBase = function(self, t)
local drain = t.getNegativeDrain(self, t)
if self:getNegative() < drain then return end
local tgts = {}
local grids = core.fov.circle_grids(self.x, self.y, 5, true)
for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
local a = game.level.map(x, y, Map.ACTOR)
if a and self:reactionToward(a) < 0 then
tgts[#tgts+1] = a
end
end end
local drain = t.getNegativeDrain(self, t)
-- Randomly take targets
local tg = {type="hit", range=self:getTalentRange(t), talent=t}
if #tgts <= 0 then break end
if self:getNegative() - 1 < drain then break end
local a, id = rng.table(tgts)
table.remove(tgts, id)
self:project(tg, a.x, a.y, DamageType.DARKNESS, rng.avg(1, self:spellCrit(t.getDamage(self, t)), 3))
game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(a.x-self.x), math.abs(a.y-self.y)), "shadow_beam", {tx=a.x-self.x, ty=a.y-self.y})
game:playSoundNear(self, "talents/spell_generic")
sustain_slots = 'celestial_hymn',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic")
game.logSeen(self, "#DARK_GREY#A shroud of shadow dances around %s!", self:getName())
return { name=("%s's %s"):tformat(self:getName():capitalize(), t.name)
}
end,
deactivate = function(self, t, p)
game.logSeen(self, "#DARK_GREY#The shroud of shadows around %s disappears.", self:getName())
return true
end,
info = function(self, t)
local targetcount = t.getTargetCount(self, t)
local damage = t.getDamage(self, t)
local drain = t.getNegativeDrain(self, t)
return ([[Chant the glory of the Moons, conjuring a shroud of dancing shadows that follows you as long as this spell is active.
Each turn, a shadowy beam will hit up to %d of your foes within radius 5 for 1 to %0.2f damage.
This powerful spell will drain %0.1f negative energy for each beam; no beam will fire if your negative energy is too low.
The damage will increase with your Spellpower.]]):
tformat(targetcount, damDesc(self, DamageType.DARKNESS, damage), drain)
newTalent{
name = "Hymn Acolyte",
type = {"celestial/hymns", 1},
require = divi_req1,
points = 5,
mode = "passive",
passives = function(self, t)
self:setTalentTypeMastery("celestial/hymns-hymns", self:getTalentMastery(t))
end,
on_learn = function(self, t)
self:learnTalent(self.T_HYMN_OF_SHADOWS, true, nil, {no_unlearn=true})
self:learnTalent(self.T_HYMN_OF_DETECTION, true, nil, {no_unlearn=true})
self:learnTalent(self.T_HYMN_OF_PERSEVERANCE, true, nil, {no_unlearn=true})
end,
on_unlearn = function(self, t)
self:unlearnTalent(self.T_HYMN_OF_SHADOWS)
self:unlearnTalent(self.T_HYMN_OF_DETECTION)
self:unlearnTalent(self.T_HYMN_OF_PERSEVERANCE)
end,
info = function(self, t)
local ret = ""
local old1 = self.talents[self.T_HYMN_OF_SHADOWS]
local old2 = self.talents[self.T_HYMN_OF_DETECTION]
local old3 = self.talents[self.T_HYMN_OF_PERSEVERANCE]
self.talents[self.T_HYMN_OF_SHADOWS] = (self.talents[t.id] or 0)
self.talents[self.T_HYMN_OF_DETECTION] = (self.talents[t.id] or 0)
self.talents[self.T_HYMN_OF_PERSEVERANCE] = (self.talents[t.id] or 0)
pcall(function() -- Be very paranoid, even if some addon or whatever manage to make that crash, we still restore values
local t1 = self:getTalentFromId(self.T_HYMN_OF_SHADOWS)
local t2 = self:getTalentFromId(self.T_HYMN_OF_DETECTION)
local t3 = self:getTalentFromId(self.T_HYMN_OF_PERSEVERANCE)
ret = ([[You have learned to sing the praises of the Moons, in the form of three defensive Hymns:
Hymn of Shadows: Increases your movement speed by %d%% and your spell casting speed by %d%%.
Hymn of Detection: Increases your ability to see stealthy creatures by %d and invisible creatures by %d, and increases your critical power by %d%%.
Hymn of Perseverance: Increases your resistance to stun, confusion and blinding by %d%%.
You may only have one Hymn active at a time.]]):
tformat(t1.moveSpeed(self, t1), t1.castSpeed(self, t1), t2.getSeeStealth(self, t2), t2.getSeeInvisible(self, t2), t2.critPower(self, t2), t3.getImmunities(self, t3)*100)
end)
self.talents[self.T_HYMN_OF_SHADOWS] = old1
self.talents[self.T_HYMN_OF_DETECTION] = old2
self.talents[self.T_HYMN_OF_PERSEVERANCE] = old3
return ret
end,
}
newTalent{
name = "Hymn Incantor",
require = divi_req2,
points = 5,
mode = "passive",
getDamageOnMeleeHit = function(self, t) return self:combatTalentSpellDamage(t, 10, 30) end,
getDarkDamageIncrease = function(self, t) return self:combatTalentSpellDamage(t, 10, 30) end,
info = function(self, t)
return ([[Your Hymns now focus darkness near you, which increases your darkness damage by %d%% and does %0.2f darkness damage to anyone who hits you in melee.
These values scale with your Spellpower.]]):tformat(t.getDarkDamageIncrease(self, t), damDesc(self, DamageType.DARKNESS, t.getDamageOnMeleeHit(self, t)))
end,
}
-- Remember that Hymns can be swapped instantly.
newTalent{
name = "Hymn Adept",
require = divi_req3,
points = 5,
mode = "passive",
getBonusInfravision = function(self, t) return math.floor(self:combatTalentScale(t, 0.75, 3.5, 0.75)) end,
getSpeed = function(self, t) return self:combatTalentSpellDamage(t, 300, 600) end,
shieldDur = function(self, t) return self:combatTalentSpellDamage(t, 5, 10) end,
shieldPower = function(self, t) return self:combatTalentSpellDamage(t, 50, 500) end,
invisDur = function(self, t) return self:combatTalentSpellDamage(t, 5, 10) end,

Ryan Yappert
committed
invisPower = function(self, t) return math.ceil(self:combatTalentSpellDamage(t, 20, 30)) end,
info = function(self, t)
return ([[Your skill in Hymns now improves your sight in darkness, increasing your infravision radius by %d.
Also, when you end a Hymn, you will gain a buff of a type based on which Hymn you ended.
Hymn of Shadows increases your movement speed by %d%% for one turn.
Hymn of Detection makes you invisible (power %d) for %d turns.
Hymn of Perseverance grants a damage shield (power %d) for %d turns.]]):tformat(t.getBonusInfravision(self, t), t.getSpeed(self, t), t.invisPower(self, t), t.invisDur(self, t), self:getShieldAmount(t.shieldPower(self, t)), self:getShieldDuration(t.shieldDur(self, t)))
end,
}
newTalent{
name = "Hymn Nocturnalist",
cooldown = 10,
sustain_negative = 5,
tactical = { SELF = {NEGATIVE = 0.5}, -- this assumes a Hymn is active
ATTACKAREA = { DARKNESS = 1 },
DISABLE = { blind = 0.5},
NEGATIVE = function(self, t, target)
return t.getNegativeDrain(self, t)/15 -- negated for foes
end
},
target = function(self, t) -- for AI only
DarkGod
committed
return {type="ball", friendlyfire=false, friendlyblock=false, radius=self:getTalentRange(t), range=0, talent=t}
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 1, 50) end,
getTargetCount = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
getNegativeDrain = function(self, t) return 5 end,
do_beams = function(self, t)
if self:getNegative() < t.getNegativeDrain(self, t) then return end
local tgts = {}
local grids = core.fov.circle_grids(self.x, self.y, 5, true)
for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
local a = game.level.map(x, y, Map.ACTOR)
if a and self:reactionToward(a) < 0 then
tgts[#tgts+1] = a
end
end end
if #tgts <= 0 then return end
local drain = t.getNegativeDrain(self, t)
self:incNegative(-drain) -- Make sure to pay before Corona procs
local dam = self:spellCrit(t.getDamage(self, t))
-- Randomly take targets
local tg = {type="hit", range=self:getTalentRange(t), talent=t}
for i = 1, t.getTargetCount(self, t) do
if #tgts <= 0 then break end
local a, id = rng.table(tgts)
table.remove(tgts, id)
self:project(tg, a.x, a.y, DamageType.DARKNESS_BLIND, dam)
game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(a.x-self.x), math.abs(a.y-self.y)), "shadow_beam", {tx=a.x-self.x, ty=a.y-self.y})
game:playSoundNear(self, "talents/spell_generic")
end
end,
game:onTickEnd(function()
self.turn_procs.resetting_talents = true
local hymn_id = self.sustain_slots and self.sustain_slots.celestial_hymn
if self:isTalentActive(hymn_id) then
self:forceUseTalent(hymn_id, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true})
self:forceUseTalent(hymn_id, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true})
end
self.turn_procs.resetting_talents = nil
end)
game:onTickEnd(function()
self.turn_procs.resetting_talents = true
local hymn_id = self.sustain_slots and self.sustain_slots.celestial_hymn
if self:isTalentActive(hymn_id) then
self:forceUseTalent(hymn_id, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true})
self:forceUseTalent(hymn_id, {ignore_energy=true, ignore_cd=true, no_talent_fail=true, silent=true})
end
self.turn_procs.resetting_talents = nil
end)
return ([[Your passion for singing the praises of the Moons reaches its zenith.
Your Hymns now fire shadowy beams that will hit up to %d of your foes within radius 5 for %0.2f damage, with a 25%% chance of blinding.
This powerful effect will drain %0.1f negative energy each time it fires at at least 1 target; no beam will fire if your negative energy is too low.
These values scale with your Spellpower.]]):tformat(t.getTargetCount(self, t), damDesc(self, DamageType.DARKNESS, t.getDamage(self, t)), t.getNegativeDrain(self, t))