Skip to content
Snippets Groups Projects
Forked from tome / Tales of MajEyal
1174 commits behind the upstream repository.
kinetic-mastery.lua 11.17 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 = "Transcendent Telekinesis",
	type = {"psionic/kinetic-mastery", 1},
	require = psi_wil_high1,
	points = 5,
	psi = 20,
	cooldown = 30,
	tactical = { BUFF = 3 },
	getPower = function(self, t) return self:combatTalentMindDamage(t, 10, 30) end,
	getPenetration = function(self, t) return self:combatLimit(self:combatTalentMindDamage(t, 10, 20), 100, 4.2, 4.2, 13.4, 13.4) end, -- Limit < 100%
	getDuration = function(self, t) return 5 end, --Limit < 30
	action = function(self, t)
		self:setEffect(self.EFF_TRANSCENDENT_TELEKINESIS, t.getDuration(self, t), {power=t.getPower(self, t), penetration = t.getPenetration(self, t)})
		self:removeEffect(self.EFF_TRANSCENDENT_PYROKINESIS)
		self:removeEffect(self.EFF_TRANSCENDENT_ELECTROKINESIS)
		self:alterTalentCoolingdown(self.T_KINETIC_LEECH, -1000)
		self:alterTalentCoolingdown(self.T_KINETIC_STRIKE, -1000)
		self:alterTalentCoolingdown(self.T_KINETIC_AURA, -1000)
		self:alterTalentCoolingdown(self.T_KINETIC_SHIELD, -1000)
		self:alterTalentCoolingdown(self.T_MINDLASH, -1000)
		return true
	end,
	info = function(self, t)
		return ([[For %d turns your telekinesis transcends your normal limits, increasing your Physical damage by %d%% and your Physical resistance penetration by %d%%.
		In addition:
		The cooldowns of Kinetic Shield, Kinetic Leech, Kinetic Aura, Kinetic Strike and Mindlash are reset.
		Kinetic Aura effects will have their radius increased by 1.
		Your Kinetic Shield will have 100%% absorption efficiency and will absorb twice the normal amount of damage.
		Mindlash will also inflict stun.
		Kinetic Leech will put enemies to sleep.
		Kinetic Strike will hit 2 adjacent enemies in a sweeping attack.
		The damage bonus and resistance penetration scale with your Mindpower.
		Only one Transcendent talent may be in effect at a time.]]):tformat(t.getDuration(self, t), t.getPower(self, t), t.getPenetration(self, t))
	end,
}

newTalent{
	name = "Kinetic Surge", image = "talents/telekinetic_throw.png",
	type = {"psionic/kinetic-mastery", 2},
	require = psi_wil_high2,
	points = 5,
	random_ego = "attack",
	cooldown = 15,
	psi = 20,
	tactical = { CLOSEIN = 2, ATTACK = { PHYSICAL = 2 }, ESCAPE = 2 },
	range = function(self, t) return math.floor(self:combatTalentLimit(t, 10, 6, 9)) end,
	getDamage = function (self, t)
		return math.floor(self:combatTalentMindDamage(t, 20, 180))
	end,
	getKBResistPen = function(self, t) return self:combatTalentLimit(t, 100, 25, 45) end,
	requires_target = true,
	target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=2, selffire=false, talent=t} end,
	action = function(self, t)
		local tg = {type="hit", range=1, nowarning=true, nolock=true, simple_dir_request=true }
		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) > 1 then return nil end
				
		if target ~= self then
			local tg = self:getTalentTarget(t)
			local x, y = self:getTarget(tg)
			if not x or not y then return nil end
			
			local dam = self:mindCrit(t.getDamage(self, t))

			if target:canBe("knockback") or rng.percent(t.getKBResistPen(self, t)) then
				self:project({type="hit", range=tg.range}, target.x, target.y, DamageType.PHYSICAL, dam) --Direct Damage
				
				local tx, ty = util.findFreeGrid(x, y, 5, true, {[Map.ACTOR]=true})
				if tx and ty then
					local ox, oy = target.x, target.y
					target:move(tx, ty, true)
					if config.settings.tome.smooth_move > 0 then
						target:resetMoveAnim()
						target:setMoveAnim(ox, oy, 8, 5)
					end
				end
				tg.act_exclude = {[target.uid]=true} -- Don't hit primary target with AOE
				self:project(tg, target.x, target.y, DamageType.MINDKNOCKBACK, dam/2) --AOE damage
				if target:canBe("stun") then
					target:setEffect(target.EFF_STUNNED, math.floor(self:getTalentRange(t) / 2), {apply_power=self:combatMindpower()})
				else
					game.logSeen(target, "%s resists the stun!", target:getName():capitalize())
				end
			else --If the target resists the knockback, do half damage to it.
				target:logCombat(self, "#YELLOW##Source# resists #Target#'s throw!")
				self:project({type="hit", range=tg.range}, target.x, target.y, DamageType.PHYSICAL, dam/2)
			end
		else
			local tg = {type="beam", range=self:getTalentRange(t), nolock=true, talent=t, display={particle="bolt_earth", trail="earthtrail"}}
			local x, y = self:getTarget(tg)
			if not x or not y then return nil end
			if core.fov.distance(self.x, self.y, x, y) > tg.range then return nil end
			
			local dam = self:mindCrit(t.getDamage(self, t))
			
			for i = 1, math.floor(self:getTalentRange(t) / 2) do
				self:project(tg, x, y, DamageType.DIG, 1)
			end
			self:project(tg, x, y, DamageType.MINDKNOCKBACK, dam)
			local _ _, x, y = self:canProject(tg, x, y)
			game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam", {tx=x-self.x, ty=y-self.y})
			game:playSoundNear(self, "talents/lightning")
			local block_actor = function(_, bx, by) return game.level.map:checkEntity(bx, by, engine.Map.TERRAIN, "block_move", self) end
			local l = self:lineFOV(x, y, block_actor)
			local lx, ly, is_corner_blocked = l:step()
			local tx, ty = self.x, self.y
			while lx and ly do
				if is_corner_blocked or block_actor(_, lx, ly) then break end
				tx, ty = lx, ly
				lx, ly, is_corner_blocked = l:step()
			end
			--self:move(tx, ty, true)
			local fx, fy = util.findFreeGrid(tx, ty, 5, true, {[Map.ACTOR]=true})
			if fx then
				self:move(fx, fy, true)
			end
			return true
		end
		return true
	end,
	info = function(self, t)
		local range = self:getTalentRange(t)
		local dam = damDesc(self, DamageType.PHYSICAL, t.getDamage(self, t))
		return ([[Build telekinetic power and dump it into an adjacent creature or yourself.
		This will launch them to a targeted location in radius %d.

		Launched enemies take %0.1f Physical damage and are stunned for %d turns upon landing.
		When the target lands, creatures within radius 2 take %0.1f Physical damage and are knocked away from you.
		This talent ignores %d%% of the knockback resistance of the thrown target, which takes half damage if it resists being thrown.

		When used on yourself, you will launch in a straight line, knocking enemies flying and doing %0.1f Physical damage to each.
		You can break through %d walls while doing this.
		The damage and range increases with Mindpower.]]):
		tformat(range, dam, math.floor(range/2), dam/2, t.getKBResistPen(self, t), dam, math.floor(range/2))
	end,
}


newTalent{
	name = "Deflect Projectiles",
	type = {"psionic/kinetic-mastery", 3},
	require = psi_wil_high3, 
	points = 5,
	mode = "sustained", no_sustain_autoreset = true,
	sustain_psi = 25,
	cooldown = 10,
	range = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8, "log")) end, 
	radius = 10,
	target = function(self, t)
		return {type="hit", range=self:getTalentRange(t), selffire=false, talent=t}
	end,
	getEvasion = function(self, t) return self:combatTalentLimit(t, 90, 15, 40), self:getTalentLevel(t) >= 4 and 2 or 1 end, -- Limit chance <90%
	activate = function(self, t)
		local chance, spread = t.getEvasion(self, t)
		return {
			chance = self:addTemporaryValue("projectile_evasion", chance),
			slow = self:addTemporaryValue("slow_projectiles", chance),
			spread = self:addTemporaryValue("projectile_evasion_spread", spread),
		}
	end,
	deactivate = function(self, t, p)
		self:removeTemporaryValue("projectile_evasion", p.chance)
		self:removeTemporaryValue("projectile_evasion_spread", p.spread)
		self:removeTemporaryValue("slow_projectiles", p.slow)
		if self:attr("save_cleanup") then return true end
	
		local tg = self:getTalentTarget(t)
		local tx, ty = self:getTarget(tg)
		if not tx or not ty then return nil end
		
		local grids = core.fov.circle_grids(self.x, self.y, self:getTalentRadius(t), true)
		for x, yy in pairs(grids) do for y, _ in pairs(grids[x]) do
			local i = 0
			local p = game.level.map(x, y, Map.PROJECTILE+i)
			while p do
				if p.project and p.project.def.typ.source_actor ~= self then
					p.project.def.typ.line_function = core.fov.line(p.x, p.y, tx, ty)
				end
				
				i = i + 1
				p = game.level.map(x, y, Map.PROJECTILE+i)
			end
		end end

		game.level.map:particleEmitter(self.x, self.y, self:getTalentRadius(t), "shout", {additive=true, life=10, size=3, distorion_factor=0.0, radius=self:getTalentRadius(t), nb_circles=4, rm=0.8, rM=1, gm=0, gM=0, bm=0.8, bM=1.0, am=0.4, aM=0.6})
		
		return true
	end,
	info = function(self, t)
		local chance, spread = t.getEvasion(self, t)
		return ([[You learn to devote a portion of your attention to mentally swatting, grabbing, or otherwise deflecting incoming projectiles.
		All projectiles targeting you have a %d%% chance to instead target another spot within radius %d and move %d%% slower.
		If you choose, you can use your mind to grab all projectiles within radius 10 of you and hurl them toward any location within range %d of you, but this will break your concentration.
		To do this, deactivate this sustained talent.]]):
		tformat(chance, spread, chance, self:getTalentRange(t))
	end,
}

newTalent{
	name = "Implode",
	type = {"psionic/kinetic-mastery", 4},
	require = psi_wil_high4,
	points = 5,
	random_ego = "attack",
	cooldown = 20,
	psi = 35,
	tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = 2 },
	range = 5,
	getDuration = function (self, t)
		return math.ceil(self:combatTalentMindDamage(t, 2, 6))
	end,
	getDamage = function (self, t)
		return math.floor(self:combatTalentMindDamage(t, 66, 132))
	end,
	requires_target = true,
	target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=0, selffire=false, talent=t} end,
	action = function(self, t)
		local dur = t.getDuration(self, t)
		local dam = t.getDamage(self, t)
		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(px, py)
			DamageType:get(DamageType.IMPLOSION).projector(self, px, py, DamageType.IMPLOSION, {dur=dur, dam=dam})
			local act = game.level.map(px, py, Map.ACTOR)
			if not act then return end
			act:setEffect(self.EFF_PSIONIC_BIND, dur, {power=1, apply_power=self:combatMindpower()})
		end)
		
		return true
	end,
	info = function(self, t)
		local dur = t.getDuration(self, t)
		local dam = t.getDamage(self, t)
		return ([[Bind the target mercilessly with constant, bone-shattering pressure, pinning and slowing it by 50%% for %d turns and dealing %0.1f Physical damage each turn.
		The duration and damage improve with Mindpower.]]):
		tformat(dur, damDesc(self, DamageType.PHYSICAL, dam))
	end,
}