Forked from
tome / Tales of MajEyal
427 commits behind the upstream repository.
-
K'van authored
Update the on_pre_use for Vault to check for a shield only. The action only uses the shield, so we don't need to check for sling and ammo as well.
K'van authoredUpdate the on_pre_use for Vault to check for a shield only. The action only uses the shield, so we don't need to check for sling and ammo as well.
agility.lua 11.49 KiB
-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2009 - 2019 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 DamageType = require "engine.DamageType"
local Object = require "engine.Object"
local Map = require "engine.Map"
local preUse = function(self, t, silent)
if not self:hasShield() or not archerPreUse(self, t, true) then
if not silent then game.logPlayer("You require a ranged weapon and a shield to use this talent.") end
return false
end
return true
end
archery_range = Talents.main_env.archery_range
newTalent{
name = "Agile Defense",
type = {"technique/agility", 1},
require = techs_dex_req_high1,
points = 5,
no_unlearn_last = true,
mode = "passive",
getChance = function(self, t) return math.floor(self:combatTalentScale(t, 15, 45)) end,
on_learn = function(self, t)
self:attr("allow_wear_shield", 1)
self:attr("show_shield_combat", 1)
end,
on_unlearn = function(self, t)
self:attr("allow_wear_shield", -1)
self:attr("show_shield_combat", 1)
end,
callbackOnTakeDamage = function(self, t, src, x, y, type, dam, tmp)
local chance = t.getChance(self, t)
if not rng.percent(chance) then return end
--this might be worth doing later, but for now let it block DoTs
--local psrc = src.__project_source
--if psrc then
-- local kind = util.getval(psrc.getEntityKind)
-- if kind == "projectile" or kind == "trap" or kind == "object" then
-- else
-- return
-- end
--end
local lastdam = dam
local shield = self:hasShield()
if not shield then return end
local t = self:getTalentFromId(self.T_BLOCK)
if self:isTalentCoolingDown(t) then return end
local reduce = t.getBlockValue(self,t)/2
dam = math.max(dam - reduce, 0)
print("[PROJECTOR] after static reduction dam", dam)
game:delayedLogDamage(src or self, self, 0, ("%s(%d deflected)#LAST#"):tformat(DamageType:get(type).text_color or "#aaaaaa#", lastdam - dam), false)
return {dam=dam}
end,
info = function(self, t)
local chance = t.getChance(self, t)
return ([[You are trained in an agile, mobile fighting technique combining sling and shield. This allows shields to be equipped, using Dexterity instead of Strength as a requirement.
While you have a shield equip and your Block talent is not on cooldown, you have a %d%% chance to deflect any incoming damage, reducing it by 50%% of your shield’s block value.]])
:tformat(chance)
end,
}
newTalent{
name = "Vault",
type = {"technique/agility", 2},
require = techs_dex_req_high2,
points = 5,
random_ego = "attack",
cooldown = 10,
stamina = 15,
requires_target = true,
tactical = { ATTACK = 2, ESCAPE = 1, DISABLE = { stun = 1 } },
target = function(self, t) return {type="hit", range=self:getTalentRange(t)} end,
range = 1,
is_special_melee = true,
on_pre_use = function(self, t, silent)
if not self:hasShield() then
if not silent then
game.logPlayer(self, "You require a shield to use this talent.")
end
return false
end
return true
end,
getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1.5, 3.0) end,
getDist = function(self, t) return math.floor(self:combatTalentScale(t, 3, 5)) end,
action = function(self, t)
local shield, shield_combat = self:hasShield()
if not shield then
game.logPlayer(self, "You require a shield to use this talent.")
return nil
end
local tg = self:getTalentTarget(t)
local x, y, target = self:getTarget(tg)
if not target or not self:canProject(tg, x, y) then return nil end
-- Leap
local tg = {type="hit", nolock=true, range=t.getDist(self,t)}
local x, y = self:getTarget(tg)
if not x or not y then return nil end
local _ _, x, y = self:canProject(tg, x, y)
if game.level.map(x, y, Map.ACTOR) then
x, y = util.findFreeGrid(x, y, 1, true, {[Map.ACTOR]=true})
if not x then return end
end
if game.level.map:checkAllEntities(x, y, "block_move") then return end
-- Modify shield combat to use dex.
local combat = table.clone(shield_combat, true)
if combat.dammod.str and combat.dammod.str > 0 then
combat.dammod.dex = (combat.dammod.dex or 0) + combat.dammod.str
combat.dammod.str = nil
end
-- First attack with shield
local speed, hit = self:attackTargetWith(target, combat, nil, t.getDamage(self, t))
-- Daze
if hit then
if target:canBe("stun") then
target:setEffect(target.EFF_DAZED, 2, {apply_power=self:combatPhysicalpower()})
else
game.logSeen(target, "%s resists the daze!", target:getName():capitalize())
end
end
local ox, oy = self.x, self.y
self:move(x, y, true)
if config.settings.tome.smooth_move > 0 then
self:resetMoveAnim()
self:setMoveAnim(ox, oy, 8, 5)
end
-- Free block?
if self:getTalentLevel(t) >= 5 then
self:forceUseTalent(self.T_BLOCK, {ignore_energy=true, ignore_cd = true, silent = true})
end
return true
end,
info = function(self, t)
local dam = t.getDamage(self, t) * 100
local range = t.getDist(self, t)
return ([[Leap onto an adjacent target with your shield, striking them for %d%% damage and dazing them for 2 turns, then using them as a springboard to leap to a tile within range %d.
The shield bash will use Dexterity instead of Strength for the shield's bonus damage.
At talent level 5, you will immediately enter a blocking stance on landing.]])
:tformat(dam, range)
end,
}
newTalent{
name = "Bull Shot",
type = {"technique/agility", 3},
no_energy = "fake",
points = 5,
random_ego = "attack",
cooldown = 8,
stamina = 18,
require = techs_dex_req_high3,
range = archery_range,
tactical = { ATTACK = { weapon = 1 } },
requires_target = true,
on_pre_use = function(self, t, silent)
if not archerPreUse(self, t, silent, "sling") then return false end
if self:attr("never_move") then return false end
return true
end,
callbackOnMove = function(self, t, moved, force, ox, oy)
if (self.x == ox and self.y == oy) or force then return end
local cooldown = self.talents_cd[t.id] or 0
if cooldown > 0 then
self.talents_cd[t.id] = math.max(cooldown - 1, 0)
end
end,
getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1.0, 2.2) end, --high damage, high opportunity cost
getDist = function(self, t) if self:getTalentLevel(t) >= 3 then return 2 else return 1 end end,
archery_onhit = function(self, t, target, x, y)
if not target or not target:canBe("knockback") then return end
target:knockback(self.x, self.y, t.getDist(self, 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 core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end
local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, Map.TERRAIN, "block_move", self) end
local linestep = self:lineFOV(x, y, block_actor)
local tx, ty, lx, ly, is_corner_blocked
repeat -- make sure each tile is passable
tx, ty = lx, ly
lx, ly, is_corner_blocked = linestep:step()
until is_corner_blocked or not lx or not ly or game.level.map:checkAllEntities(lx, ly, "block_move", self)
if not tx or core.fov.distance(self.x, self.y, tx, ty) < 1 then
game.logPlayer(self, "You are too close to build up momentum!")
return
end
if not tx or not ty or core.fov.distance(x, y, tx, ty) > 1 then return nil end
local ox, oy = self.x, self.y
self:move(tx, ty, true)
if config.settings.tome.smooth_move > 0 then
self:resetMoveAnim()
self:setMoveAnim(ox, oy, 8, 5)
end
-- Attack ?
if core.fov.distance(self.x, self.y, x, y) == 1 then
local targets = self:archeryAcquireTargets(nil, {one_shot=true, x=x, y=y})
if targets then
self:archeryShoot(targets, t, nil, {mult=t.getDamage(self,t)})
end
end
return true
end,
info = function(self, t)
return ([[You rush toward your foe, readying your shot. If you reach the enemy, you release the shot, imbuing it with great power.
The shot does %d%% weapon damage and knocks back your target by %d.
The cooldown of this talent is reduced by 1 each time you move.
This requires a sling to use.]]):
tformat(t.getDamage(self,t)*100, t.getDist(self, t))
end,
}
newTalent{
name = "Rapid Shot",
type = {"technique/agility", 4},
points = 5,
mode = "sustained",
require = techs_dex_req_high4,
cooldown = 30,
sustain_stamina = 50,
no_energy = true,
tactical = { BUFF = 2 },
on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
getAttackSpeed = function(self, t) return math.floor(self:combatTalentLimit(t, 35, 15, 30))/100 end,
getMovementSpeed = function(self, t) return math.floor(self:combatTalentScale(t, 25, 50))/100 end,
getTurn = function(self, t) return math.floor(self:combatTalentLimit(t, 20, 5, 12)) end,
on_pre_use = function(self, t, silent)
if not archerPreUse(self, t, silent, "sling") then return false end
return true
end,
callbackOnArcheryAttack = function(self, t, target, hitted)
local dist = math.max(0, core.fov.distance(self.x, self.y, target.x, target.y) - 3)
if hitted and not target.turn_procs.rapid_fire and dist < 5 then
target.turn_procs.rapid_fire = true
turn = t.getTurn(self,t)/100
energy = turn - (turn * dist * 0.2)
self.energy.value = self.energy.value + game.energy_to_act*energy
end
game:onTickEnd(function()
self:setEffect(self.EFF_RAPID_MOVEMENT, 1, {src=self, power=t.getMovementSpeed(self,t)})
end)
end,
activate = function(self, t)
local weapon = self:hasArcheryWeapon()
if not weapon then
game.logPlayer(self, "You cannot use Rapid Fire without a bow or sling!")
return nil
end
local speed = t.getAttackSpeed(self, t)
local ret = {
speed = self:addTemporaryValue("combat_physspeed", speed),
}
if core.shader.active() then
self:talentParticles(ret, {type="shader_shield", args={toback=true, size_factor=1.5, rotspeed=7, img="rapid_shot_rotating_tentacles"}, shader={type="tentacles", backgroundLayersCount=-4, wobblingType=0, appearTime=0.4, time_factor=600, noup=2.0}})
self:talentParticles(ret, {type="shader_shield", args={toback=false, size_factor=1.5, rotspeed=7, img="rapid_shot_rotating_tentacles"}, shader={type="tentacles", backgroundLayersCount=-4, wobblingType=0, appearTime=0.4, time_factor=600, noup=1.0}})
end
return ret
end,
deactivate = function(self, t, p)
self:removeTemporaryValue("combat_physspeed", p.speed)
return true
end,
info = function(self, t)
local atk = t.getAttackSpeed(self,t)*100
local move = t.getMovementSpeed(self,t)*100
local turn = t.getTurn(self,t)
return ([[Enter a fluid, mobile shooting stance that excels at close combat. Your ranged attack speed is increased by %d%% and each time you shoot you gain %d%% increased movement speed for 2 turns.
Ranged attacks against targets will also grant you up to %d%% of a turn. This is 100%% effective against targets within 3 tiles, and decreases by 20%% for each tile beyond that (to 0%% at 8 tiles). This cannot occur more than once per turn.
Requires a sling to use.]]):
tformat(atk, move, turn)
end,
}