reflexes.lua
10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2009 - 2014 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 weaponCheck = function(self, weapon, ammo, silent, weapon_type)
if not weapon then
if not silent then
-- ammo contains error message
game.logPlayer(self, ({
["disarmed"] = "You are currently disarmed and cannot use this talent.",
["no shooter"] = ("You require a %s to use this talent."):format(weapon_type or "missile launcher"),
["no ammo"] = "You require ammo to use this talent.",
["bad ammo"] = "Your ammo cannot be used.",
["incompatible ammo"] = "Your ammo is incompatible with your missile launcher.",
["incompatible missile launcher"] = ("You require a %s to use this talent."):format(weapon_type or "bow"),
})[ammo] or "You require a missile launcher and ammo for this talent.")
end
return false
else
local infinite = ammo and ammo.infinite or self:attr("infinite_ammo")
if not ammo or (ammo.combat.shots_left <= 0 and not infinite) then
if not silent then game.logPlayer(self, "You do not have enough ammo left!") end
return false
end
end
return true
end
local archerPreUse = function(self, t, silent, weapon_type)
local weapon, ammo, offweapon, pf_weapon = self:hasArcheryWeapon(weapon_type)
weapon = weapon or pf_weapon
return weaponCheck(self, weapon, ammo, silent, weapon_type)
end
local wardenPreUse = function(self, t, silent, weapon_type)
local weapon, ammo, offweapon, pf_weapon = self:hasArcheryWeapon(weapon_type)
weapon = weapon or pf_weapon
if self:attr("warden_swap") and not weapon and weapon_type == nil or weapon_type == "bow" then
weapon, ammo = doWardenPreUse(self, "bow")
end
return weaponCheck(self, weapon, ammo, silent, weapon_type)
end
Talents.archerPreUse = archerPreUse
Talents.wardenPreUse = wardenPreUse
archery_range = Talents.main_env.archery_range
newTalent{
name = "Shoot Down",
type = {"technique/reflexes", 1},
no_energy = true,
points = 5,
cooldown = 4,
stamina = 8,
range = archery_range,
require = techs_dex_req1,
onAIGetTarget = function(self, t)
local tgts = {}
self:project({type="ball", radius=self:getTalentRange(t)}, self.x, self.y, function(px, py)
local tgt = game.level.map(px, py, Map.PROJECTILE)
if tgt and (not tgt.src or self:reactionToward(tgt.src) < 0) then tgts[#tgts+1] = {x=px, y=py, tgt=tgt, dist=core.fov.distance(self.x, self.y, px, py)} end
end)
table.sort(tgts, function(a, b) return a.dist < b.dist end)
if #tgts > 0 then return tgts[1].x, tgts[1].y, tgts[1].tgt end
end,
on_pre_use_ai = function(self, t, silent) return t.onAIGetTarget(self, t) and true or false end,
on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
requires_target = true,
getNb = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
target = function(self, t)
return {type="bolt", range=self:getTalentRange(t), scan_on=engine.Map.PROJECTILE, no_first_target_filter=true}
end,
getSlow = function(self,t) return math.min(90, self:combatTalentScale(t, 15, 50)) end,
passives = function(self, t, p)
self:talentTemporaryValue(p, "slow_projectiles", t.getSlow(self, t))
end,
tactical = {SPECIAL=10},
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
self:logCombat(proj, "#Source# shoots down '#Target#'!")
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.
In addition, your heightened senses also reduce the speed of incoming projectiles by %d%%, and prevents your own projectiles from striking you.]]):
format(t.getNb(self, t), t.getSlow(self,t))
end,
}
newTalent{
name = "Intuitive Shots",
type = {"technique/reflexes", 2},
mode = "sustained",
points = 5,
cooldown = 10,
sustain_stamina = 30,
no_energy = true,
require = techs_dex_req2,
tactical = { BUFF = 2 },
getChance = function(self, t) return math.floor(self:combatTalentLimit(t, 40, 10, 30)) end,
getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.2, 0.6) end,
-- called by _M:attackTarget in mod.class.interface.Combat.lua
proc = function(self, t, target)
if not rng.percent(t.getChance(self,t)) then return end
local old = self.energy.value
local weapon, ammo = self:hasArcheryWeapon()
if not weapon then return end
local targets = self:archeryAcquireTargets(nil, {one_shot=true, x=target.x, y=target.y}) --Ammo check done here
if not targets then return end
if not target.turn_procs.intuitive_shots and not (self:isTalentActive(self.T_CONCEALMENT) or self:hasEffect(self.EFF_WILD_SPEED) or self:hasEffect(self.EFF_ESCAPE)) then self:archeryShoot(targets, t, nil, {mult=t.getDamage(self,t)}) end
target.turn_procs.intuitive_shots = true
self.energy.value = old
return true
end,
on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
activate = function(self, t)
return {}
end,
deactivate = function(self, t, p)
return true
end,
info = function(self, t)
local chance = t.getChance(self,t)
local dam = t.getDamage(self,t)*100
return ([[Activating this talent enhances your reflexes to incredible levels. Each time you are attacked in melee, you have a %d%% chance to fire off a defensive shot off in time to intercept the attack, evading it and dealing %d%% archery damage.
This cannot damage the same target more than once per turn.]])
:format(chance, dam)
end,
}
newTalent{
name = "Sentinel",
type = {"technique/reflexes", 3},
require = techs_dex_req3,
points = 5,
cooldown = function(self, t) return math.max(6, 13-(math.floor(self:getTalentLevel(t)))) end,
stamina = 25,
tactical = { DISABLE=3 },
range = archery_range,
requires_target = true,
no_npc_use = true,
fixed_cooldown = true, -- there's probably some sort of unexpected interaction that would let you chain this infinitely with cooldown reducers
getTalentCount = function(self, t) return math.floor(self:combatTalentScale(t, 1, 4.5)) end, --Limit < 100%
getCooldown = function(self, t) return math.floor(self:combatTalentScale(t, 1, 3.5)) end, --Limit < 100%
on_pre_use = function(self, t, silent) return archerPreUse(self, t, silent) end,
archery_onhit = function(self, t, target, x, y)
local tids = {}
for tid, lev in pairs(target.talents) do
local t = target:getTalentFromId(tid)
if t and not target.talents_cd[tid] and t.mode == "activated" and not t.innate then tids[#tids+1] = t end
end
local cdr = t.getCooldown(self, t)
for i = 1, t.getTalentCount(self, t) do
local t = rng.tableRemove(tids)
if not t then break end
target.talents_cd[t.id] = cdr
game.logSeen(target, "%s's %s is disrupted by the shot!", target.name:capitalize(), t.name)
end
end,
doShoot = function(self, t, eff)
if self:attr("disarmed") then self:removeEffect(self.EFF_SENTINEL) return end
local target = eff.target
local targets = self:archeryAcquireTargets({type="hit"}, {one_shot=true, x=target.x, y=target.y, infinite=true, no_energy = true})
if not targets then return end
self:archeryShoot(targets, t, {type="hit", start_x=self.x, start_y=self.y}, {mult=0.25})
target:removeEffect(target.EFF_SENTINEL)
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
target:setEffect(target.EFF_SENTINEL, 5, {src=self, target=target})
return true
end,
info = function(self, t)
local nb = t.getTalentCount(self,t)
local cd = t.getCooldown(self,t)
return ([[You take close notice of the target for the next 5 turns. If they attempt to use a non-instant talent you react with incredible speed, firing a shot dealing 25%% damage that causes the talent to fail and go on cooldown.
This shot is instant, cannot miss, and puts %d other talents on cooldown for %d turns.]]):
format(nb, cd)
end,
}
newTalent{
name = "Escape",
type = {"technique/reflexes", 4},
no_energy = true,
points = 5,
cooldown = 25,
require = techs_dex_req4,
random_ego = "defensive",
tactical = { ESCAPE = 2, DEFEND = 2 },
getDamageReduction = function(self, t) return self:combatTalentLimit(t, 70, 15, 40) end,
getSpeed = function(self, t) return self:combatTalentScale(t, 150, 350) end,
getStamina = function(self, t) return self:combatTalentScale(t, 5, 10) end,
action = function(self, t)
local power = t.getDamageReduction(self,t)
local speed = t.getSpeed(self,t)
local stamina = t.getStamina(self,t)
game:onTickEnd(function() self:setEffect(self.EFF_ESCAPE, 4, {src=self, power=power, stamina=stamina, speed=speed}) end)
return true
end,
info = function(self, t)
local power = t.getDamageReduction(self,t)
local sta = t.getStamina(self,t)
local speed = t.getSpeed(self,t)
return ([[You put all your focus into escaping combat for 4 turns. While under this effect you gain %d%% increased resistance to all damage, %0.1f increased stamina regeneration, immunity to stun, pin, daze and slowing effects and %d%% increased movement speed.
Any action other than movement will cancel this effect.]]):
format(power, sta, speed)
end,
}