Skip to content
Snippets Groups Projects
Commit e24e98a2 authored by DarkGod's avatar DarkGod
Browse files

Added the Excellence tree to Archers, granting them more utility and survivability

parent 02b8a713
No related branches found
No related tags found
No related merge requests found
Showing with 251 additions and 16 deletions
......@@ -243,6 +243,11 @@ function _M:on_move(x, y, target)
end
end
--- Premature end
function _M:terminate(x, y)
self.src:projectDoStop(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj, self.x, self.y, self)
end
--- Generate a projectile for a project() call
function _M:makeProject(src, display, def, do_move, do_act, do_stop)
display = display or {display='*'}
......
......@@ -442,17 +442,18 @@ function _M:setSpotInMotion(x, y, how)
if self.on_set_target then self:on_set_target(how) end
end
function _M:scan(dir, radius, sx, sy, filter)
function _M:scan(dir, radius, sx, sy, filter, kind)
sx = sx or self.target.x
sy = sy or self.target.y
if not sx or not sy then return end
kind = kind or engine.Map.ACTOR
radius = radius or 20
local actors = {}
local checker = function(_, x, y)
if sx == x and sy == y then return false end
if game.level.map.seens(x, y) and game.level.map(x, y, engine.Map.ACTOR) then
local a = game.level.map(x, y, engine.Map.ACTOR)
if game.level.map.seens(x, y) and game.level.map(x, y, kind) then
local a = game.level.map(x, y, kind)
if (not self.source_actor or self.source_actor:canSee(a)) and (not filter or filter(a)) then
table.insert(actors, {
......
......@@ -155,12 +155,14 @@ function _M:targetMode(v, msg, co, typ)
end
if do_scan then
local filter = nil
if type(typ) == "table" and typ.first_target and typ.first_target == "friend" then
filter = function(a) return self.player:reactionToward(a) >= 0 end
else
filter = function(a) return self.player:reactionToward(a) < 0 end
if not (type(typ) == "table" and typ.no_first_target_filter) then
if type(typ) == "table" and typ.first_target and typ.first_target == "friend" then
filter = function(a) return self.player:reactionToward(a) >= 0 end
else
filter = function(a) return self.player:reactionToward(a) < 0 end
end
end
self.target:scan(5, nil, self.player.x, self.player.y, filter)
self.target:scan(5, nil, self.player.x, self.player.y, filter, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on)
end
end
if self.target.target.x then
......@@ -200,14 +202,14 @@ function _M:targetSetupKey()
RUN_LEFT_UP = function() self.target:freemove(7) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_RIGHT_UP = function() self.target:freemove(9) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4) elseif self.target_style == "immediate" then self.target:setDirFrom(4, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(4) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT = function() if self.target_style == "lock" then self.target:scan(6) elseif self.target_style == "immediate" then self.target:setDirFrom(6, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(6) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_UP = function() if self.target_style == "lock" then self.target:scan(8) elseif self.target_style == "immediate" then self.target:setDirFrom(8, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(8) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_DOWN = function() if self.target_style == "lock" then self.target:scan(2) elseif self.target_style == "immediate" then self.target:setDirFrom(2, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(2) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_DOWN = function() if self.target_style == "lock" then self.target:scan(1) elseif self.target_style == "immediate" then self.target:setDirFrom(1, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(1) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_DOWN = function() if self.target_style == "lock" then self.target:scan(3) elseif self.target_style == "immediate" then self.target:setDirFrom(3, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(3) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_UP = function() if self.target_style == "lock" then self.target:scan(7) elseif self.target_style == "immediate" then self.target:setDirFrom(7, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(7) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_UP = function() if self.target_style == "lock" then self.target:scan(9) elseif self.target_style == "immediate" then self.target:setDirFrom(9, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(9) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(4, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(4) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT = function() if self.target_style == "lock" then self.target:scan(6, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(6, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(6) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_UP = function() if self.target_style == "lock" then self.target:scan(8, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(8, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(8) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_DOWN = function() if self.target_style == "lock" then self.target:scan(2, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(2, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(2) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_DOWN = function() if self.target_style == "lock" then self.target:scan(1, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(1, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(1) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_DOWN = function() if self.target_style == "lock" then self.target:scan(3, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(3, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(3) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_UP = function() if self.target_style == "lock" then self.target:scan(7, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(7, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(7) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_UP = function() if self.target_style == "lock" then self.target:scan(9, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(9, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(9) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_STAY = function()
if self.target_style == "immediate" then
self.target:setDirFrom(5, self.target.target.entity or self.player)
......
......@@ -115,6 +115,11 @@ function _M:attackTarget(target, damtype, mult, noenergy, force_unharmed)
game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize())
end
if target:isTalentActive(target.T_INTUITIVE_SHOTS) and rng.percent(target:callTalent(target.T_INTUITIVE_SHOTS, "getChance")) then
local ret = target:callTalent(target.T_INTUITIVE_SHOTS, "proc", self)
if ret then return false end
end
-- Change attack type if using gems
if not damtype and self:getInven(self.INVEN_GEM) then
local gems = self:getInven(self.INVEN_GEM)
......
......@@ -155,6 +155,7 @@ newBirthDescriptor{
["technique/archery-utility"]={true, 0.3},
["technique/archery-bow"]={true, 0.3},
["technique/archery-sling"]={true, 0.3},
["technique/archery-excellence"]={false, 0.3},
["technique/combat-techniques-active"]={false, -0.1},
["technique/combat-techniques-passive"]={true, -0.1},
["technique/combat-training"]={true, 0.3},
......
game/modules/tome/data/gfx/talents/bull_shot.png

4.71 KiB

game/modules/tome/data/gfx/talents/intuitive_shots.png

3.46 KiB

game/modules/tome/data/gfx/talents/shoot_down.png

3.28 KiB

game/modules/tome/data/gfx/talents/strangling_shot.png

3.12 KiB

-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2009, 2010, 2011, 2012, 2013 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
newTalent{
name = "Shoot Down",
type = {"technique/archery-excellence", 1},
no_energy = true,
points = 5,
cooldown = 4,
stamina = 20,
range = archery_range,
require = techs_dex_req_high1,
no_npc_use = true,
on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
requires_target = true,
getNb = function(self, t) return math.floor(self:getTalentLevel(t)) end,
target = function(self, t)
return {type="bolt", range=self:getTalentRange(t), scan_on=engine.Map.PROJECTILE, no_first_target_filter=true}
end,
action = function(self, t)
for i = 1, t.getNb(self, t) do
local targets = self:archeryAcquireTargets(self:getTalentTarget(t), {one_shot=true, no_energy=true})
if (not targets or #targets == 0) then if i == 1 then return else break end end
local x, y = targets[1].x, targets[1].y
local proj = game.level.map(x, y, Map.PROJECTILE)
if proj then
proj:terminate(x, y)
game.level:removeEntity(proj, true)
proj.dead = true
game.logSeen(self, "%s takes down '%s'.", self.name:capitalize(), proj.name)
end
end
return true
end,
info = function(self, t)
return ([[Your reflexes are lightning-fast, if you spot a projectile (arrow, shot, spell, ...) you can instantly shoot at it without taking a turn to take it down.
You can shoot down up to %d projectiles.]]):
format(t.getNb(self, t))
end,
}
newTalent{
name = "Bull Shot",
type = {"technique/archery-excellence", 2},
no_energy = "fake",
points = 5,
random_ego = "attack",
cooldown = 6,
stamina = 18,
require = techs_dex_req_high2,
range = archery_range,
tactical = { ATTACK = { weapon = 1 } },
requires_target = true,
on_pre_use = function(self, t, silent)
if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end
if self:attr("never_move") then return false end
return true
end,
getDist = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) 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=self:combatTalentWeaponDamage(t, 1.5, 2.8)})
end
end
return true
end,
info = function(self, t)
return ([[You rush toward your fow, readying your shot. If you reach it you release the shot, imbuying it with great power.
The shot does %d%% weapon damage and knocks back your target by %d.]]):
format(self:combatTalentWeaponDamage(t, 1.5, 2.8) * 100, t.getDist(self, t))
end,
}
newTalent{
name = "Intuitive Shots",
type = {"technique/archery-excellence", 3},
mode = "sustained",
points = 5,
cooldown = 10,
sustain_stamina = 30,
require = techs_dex_req_high3,
tactical = { BUFF = 2 },
getDist = function(self, t) return math.floor(self:combatTalentScale(t, 1, 3)) end,
getChance = function(self, t) return math.floor(self:combatTalentScale(t, 20, 50)) 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,
proc = function(self, t, target)
if not self:hasArcheryWeapon() then return end
if self.turn_procs.intuitive_shots and self.turn_procs.intuitive_shots ~= target then return end
if self.turn_procs.intuitive_shots == target then return true end
local targets = self:archeryAcquireTargets(nil, {one_shot=true, x=target.x, y=target.y})
if targets then self:archeryShoot(targets, t, nil, {mult=self:combatTalentWeaponDamage(t, 0.4, 0.9)}) end
self.turn_procs.intuitive_shots = target
return true
end,
on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
activate = function(self, t)
return {}
end,
deactivate = function(self, t, p)
end,
info = function(self, t)
return ([[You highten your reflexes to amazing levels. Each time a creature tries to hit you in melee you have %d%% chances to be fast enough to fire a shot at the attack, fully deflecting it and all others for this turn for this creature.
In addition the shot deals %d%% damage and knocks back the creature by %d.]])
:format(t.getChance(self, t), self:combatTalentWeaponDamage(t, 0.4, 0.9) * 100, t.getDist(self, t))
end,
}
newTalent{
name = "Strangling Shot",
type = {"technique/archery-excellence", 4},
no_energy = "fake",
points = 5,
random_ego = "attack",
cooldown = 15,
stamina = 20,
require = techs_dex_req_high4,
range = archery_range,
tactical = { ATTACK = { weapon = 1 }, DISABLE = { silence = 3 } },
requires_target = true,
target = function(self, t)
return {type="bolt", range=self:getTalentRange(t)}
end,
on_pre_use = function(self, t, silent) if not self:hasArcheryWeapon() then if not silent then game.logPlayer(self, "You require a bow or sling for this talent.") end return false end return true end,
getDur = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
archery_onhit = function(self, t, target, x, y)
if target:canBe("silence") then
target:setEffect(target.EFF_SILENCED, t.getDur(self, t), {apply_power=self:combatAttack()})
else
game.logSeen(target, "%s resists the strangling shot!", target.name:capitalize())
end
end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local targets = self:archeryAcquireTargets(tg, {one_shot=true})
if not targets then return end
self:archeryShoot(targets, t, tg, {mult=self:combatTalentWeaponDamage(t, 0.9, 1.7)})
return true
end,
info = function(self, t)
return ([[You fire a shot at your target's mouth (ot similar organ), doing %d%% damage and silencing it for %d turns.
The silence chance increases with your Accuracy.]])
:format(self:combatTalentWeaponDamage(t, 0.9, 1.7) * 100, t.getDur(self,t))
end,
}
......@@ -29,6 +29,7 @@ newTalentType{ allow_random=true, type="technique/archery-bow", name = "archery
newTalentType{ allow_random=true, type="technique/archery-sling", name = "archery - slings", description = "Specialized sling techniques." }
newTalentType{ allow_random=true, type="technique/archery-training", name = "archery training", description = "Generic archery techniques." }
newTalentType{ allow_random=true, type="technique/archery-utility", name = "archery prowess", description = "Specialized archery techniques to maim your targets." }
newTalentType{ allow_random=true, type="technique/archery-excellence", name = "archery excellence", min_lev = 10, description = "Specialized archery techniques that result from honed training." }
newTalentType{ allow_random=true, type="technique/superiority", name = "superiority", min_lev = 10, description = "Advanced combat techniques." }
newTalentType{ allow_random=true, type="technique/battle-tactics", name = "battle tactics", min_lev = 10, description = "Advanced combat tactics." }
newTalentType{ allow_random=true, type="technique/warcries", name = "warcries", no_silence = true, min_lev = 10, description = "Master the warcries to improve yourself and weaken others." }
......@@ -117,6 +118,27 @@ techs_dex_req5 = {
stat = { dex=function(level) return 44 + (level-1) * 2 end },
level = function(level) return 16 + (level-1) end,
}
techs_dex_req_high1 = {
stat = { dex=function(level) return 22 + (level-1) * 2 end },
level = function(level) return 10 + (level-1) end,
}
techs_dex_req_high2 = {
stat = { dex=function(level) return 30 + (level-1) * 2 end },
level = function(level) return 14 + (level-1) end,
}
techs_dex_req_high3 = {
stat = { dex=function(level) return 38 + (level-1) * 2 end },
level = function(level) return 18 + (level-1) end,
}
techs_dex_req_high4 = {
stat = { dex=function(level) return 46 + (level-1) * 2 end },
level = function(level) return 22 + (level-1) end,
}
techs_dex_req_high5 = {
stat = { dex=function(level) return 54 + (level-1) * 2 end },
level = function(level) return 26 + (level-1) end,
}
-- Generic rquires based either on str or dex
techs_strdex_req1 = function(self, t) local stat = self:getStr() >= self:getDex() and "str" or "dex"; return {
......@@ -233,6 +255,7 @@ load("/data/talents/techniques/combat-training.lua")
load("/data/talents/techniques/bow.lua")
load("/data/talents/techniques/sling.lua")
load("/data/talents/techniques/archery.lua")
load("/data/talents/techniques/excellence.lua")
load("/data/talents/techniques/magical-combat.lua")
load("/data/talents/techniques/mobility.lua")
load("/data/talents/techniques/thuggery.lua")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment