Skip to content
Snippets Groups Projects
Forked from tome / Tales of MajEyal
2436 commits behind the upstream repository.
flux.lua 8.11 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

-- EDGE TODO: Particles, Timed Effect Particles

newTalent{
	name = "Induce Anomaly",
	type = {"chronomancy/flux", 1},
	require = chrono_req1,
	points = 5,
	cooldown = 12,
	tactical = { PARADOX = 2 },
	getReduction = function(self, t) return self:combatTalentSpellDamage(t, 20, 80, getParadoxSpellpower(self, t)) end,
	anomaly_type = "no-major",
	no_energy = true,
	action = function(self, t)

		self:paradoxDoAnomaly(100, t.getReduction(self, t), {anomaly_type=t.anomaly_type, ignore_energy=true, allow_target=self:knowTalent(self.T_TWIST_FATE)})
	
		return true
	end,
	info = function(self, t)
		local reduction = t.getReduction(self, t)
		return ([[Create an anomaly, reducing your Paradox by %d.  This spell will never produce a major anomaly.
		Induced Anomalies may not be held by Twist Fate, nor do they cause held anomalies to trigger.  However upon learning Twist Fate you may target Induced Anomalies.
		The Paradox reduction will increase with your Spellpower.]]):format(reduction)
	end,
}

newTalent{
	name = "Reality Smearing",
	type = {"chronomancy/flux", 2},
	require = chrono_req2,
	mode = "sustained", 
	sustain_paradox = 0,
	points = 5,
	cooldown = 10,
	tactical = { DEFEND = 2 },
	getPercent = function(self, t) return (100 - self:combatTalentLimit(t, 80, 10, 60))/100 end, -- Limit < 20%
	getDuration = function(self, t) return getExtensionModifier(self, t, 3) end,
	damage_feedback = function(self, t, p, src)
		if p.particle and p.particle._shader and p.particle._shader.shad and src and src.x and src.y then
			local r = -rng.float(0.2, 0.4)
			local a = math.atan2(src.y - self.y, src.x - self.x)
			p.particle._shader:setUniform("impact", {math.cos(a) * r, math.sin(a) * r})
			p.particle._shader:setUniform("impact_tick", core.game.getTime())
		end
	end,
	iconOverlay = function(self, t, p)
		local val = p.rest_count or 0
		if val <= 0 then return "" end
		local fnt = "buff_font"
		return tostring(math.ceil(val)), fnt
	end,
	callbackOnHit = function(self, t, cb, src)
		local absorb = cb.value * 0.3
		local paradox = absorb * t.getPercent(self, t)
		
		self:setEffect(self.EFF_REALITY_SMEARING, t.getDuration(self, t), {paradox=paradox/t.getDuration(self, t)})
		game:delayedLogMessage(self, nil,  "reality smearing", "#LIGHT_BLUE##Source# converts damage to paradox!")
		game:delayedLogDamage(src, self, 0, ("#LIGHT_BLUE#(%d converted)#LAST#"):format(absorb), false)
		cb.value = cb.value - absorb
		
		return cb.value
	end,
	activate = function(self, t)
		game:playSoundNear(self, "talents/arcane")

		local ret = {}
		return ret
	end,
	deactivate = function(self, t, p)
		return true
	end,
	info = function(self, t)
		local ratio = t.getPercent(self, t)
		local duration = t.getDuration(self, t)
		return ([[While active 30%% of all damage you take is converted into %0.2f Paradox per point.
		The Paradox is gained over three turns.]]):
		format(ratio, duration)
	end,
}

newTalent{
	name = "Attenuate",
	type = {"chronomancy/flux", 3},
	require = chrono_req3,
	points = 5,
	cooldown = 4,
	tactical = { ATTACKAREA = { TEMPORAL = 2 } },
	range = 10,
	paradox = function (self, t) return getParadoxCost(self, t, 10) end,
	radius = function(self, t) return math.floor(self:combatTalentScale(t, 1, 2)) end,
	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 25, 290, getParadoxSpellpower(self, t)) end,
	getDuration = function(self, t) return getExtensionModifier(self, t, 4) end,
	target = function(self, t)
		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), nowarning=true, can_autoaccept=true, talent=t}
	end,
	requires_target = true,
	direct_hit = true,
	action = function(self, t)
		local tg = self:getTalentTarget(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)
		
		local damage = self:spellCrit(t.getDamage(self, t))
		self:project(tg, x, y, function(px, py)
			local target = game.level.map(px, py, Map.ACTOR)
			if not target then return end
			if target:isTalentActive(target.T_REALITY_SMEARING) then
				target:setEffect(target.EFF_ATTENUATE_BEN, t.getDuration(self, t), {power=(damage/4)*0.4, src=self})
			else
				target:setEffect(target.EFF_ATTENUATE_DET, t.getDuration(self, t), {power=damage/4, src=self, apply_power=getParadoxSpellpower(self, t)})
			end
		end)

		game.level.map:particleEmitter(x, y, tg.radius, "generic_sploom", {rm=100, rM=100, gm=200, gM=220, bm=200, bM=220, am=35, aM=90, radius=tg.radius, basenb=60})
		game:playSoundNear(self, "talents/tidalwave")

		return true
	end,
	info = function(self, t)
		local damage = t.getDamage(self, t)
		local duration = t.getDuration(self, t)
		local radius = self:getTalentRadius(t)
		return ([[Deals %0.2f temporal damage over %d turns to all targets in a radius of %d.  Targets with Reality Smearing active will instead recover %d life over four turns.
		If a target is reduced below 20%% life while Attenuate is active it may be instantly slain.
		The damage will scale with your Spellpower.]]):format(damDesc(self, DamageType.TEMPORAL, damage), duration, radius, damage *0.4)
	end,
}

newTalent{
	name = "Twist Fate",
	type = {"chronomancy/flux", 4},
	require = chrono_req4,
	points = 5,
	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 6, 20, 8)) end, -- Limit >4
	tactical = { ATTACKAREA = 2 },
	on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_TWIST_FATE) then if not silent then game.logPlayer(self, "You must have a twisted anomaly to cast this spell.") end return false end return true end,
	getDuration = function(self, t) return getExtensionModifier(self, t, math.floor(self:combatTalentScale(t, 1, 6))) end,
	doTwistFate = function(self, t, twist)
		local eff = self:hasEffect(self.EFF_TWIST_FATE)
		
		if twist then
			eff.twisted = twist
			local anom = self:getTalentFromId(eff.talent)
			
			-- make it real obvious for the player
			game.logPlayer(self, "#STEEL_BLUE#Casts %s.", anom.name)
			if self == game.player then
				game.bignews:saySimple(180, "#STEEL_BLUE#Targeting %s", anom.name)
			end
			
			-- Call the anomoly action function directly
			anom.action(self, anom)
			self:incParadox(-eff.paradox)
		end
			
		self:removeEffect(self.EFF_TWIST_FATE)
	end,
	setEffect = function(self, t, talent, paradox)
		game.logPlayer(self, "#STEEL_BLUE#You take control of %s.", self:getTalentFromId(talent).name or nil)
		self:setEffect(self.EFF_TWIST_FATE, t.getDuration(self, t), {talent=talent, paradox=paradox})
		
		game:playSoundNear(self, "talents/echo")
	end,
	action = function(self, t)
		t.doTwistFate(self, t, true)
		game:playSoundNear(self, "talents/echo")
		return true
	end,
	info = function(self, t)
		local duration = t.getDuration(self, t)
		local talent
		local t_name = "None"
		local t_info = ""
		local eff = self:hasEffect(self.EFF_TWIST_FATE)
		if eff then
			talent = self:getTalentFromId(eff.talent)
			t_name = talent.name
			t_info = talent.info(self, talent)
		end
		return ([[If Twist Fate is not on cooldown minor anomalies will be held for %d turns, allowing your spell to cast as normal.  While held you may cast Twist Fate in order to trigger the anomaly and may choose the target area.
		If a second anomaly occurs while a prior one is held or the timed effect expires the first anomaly will trigger immediately, interrupting your current turn or action.
		Paradox reductions from held anomalies occur when triggered.
				
		Current Anomaly: %s
		
		%s]]):
		format(duration, t_name, t_info)
	end,
}