PK ��U� �
changelog.txtChanges for v1.0.1:
- Void Stargazer's graphic should now load correctly (the file extension was capitalised, which works fine when loading from my working folder but breaks when loading from the uploaded .teaa :V)
- Logia of Dysnomy now properly checks if the damage source has an actual defined position (damn you poison water!)
- Fully disabled self-damage from Logia Enigma just in case (there are some corner cases where the player might block damage from a source that is not themself but shares the same position)
Changes for v1.0.2:
- Redesigned Negative Pressure somewhat; it no longer gives negative incdam and respen if too many tiles are occupied. Instead, it now grants no bonus if 2 or fewer tiles are open, and scales up the bonus from 3-8 open tiles. The maximum bonuses have also been increased to account for the difficulty in finding and maintaining an entirely empty 3x3 area.
- Undead starts are no longer overridden (should work with addon races as long as they properly set the true_undead field)
Changes for v1.0.3:
- Removed a stray file that resulted in Point Zero being overwritten by a broken copy of Terminus Sanctum, resulting in chronomancers exploding violently upon creation
Changes for v1.1.0 "A God Am I...?":
- Siphon's base debuff strength has been increased
- Disrupt's base damage has been increased (140->165)
- Disrupt's base energy gain has been increased (8->10)
- Filament's base damage has been slightly increased (300->320)
- Visage now becomes harder to trigger off of crits starting from 5 clones, and becomes increasingly difficult as the number of clones increases (might relax this a bit but let's see how this tastes for now)
- Activating Visage spawns 2 clones (down from 3)
- Visage's base damage has been reduced (50->40)
- Visage's slow has been weakened (30%->20%)
- Megalotheos's bonuses are no longer capped (as Visage is now more aggressively softcapped)
- Void Walk's energy cost has been reduced (10->5)
- Degeneration's base Temporal weakness has been increased (10->12)
- Compressed Space's base boost has been reduced (20-40 -> 16-32), but it hits this point earlier (40 -> 30 void energy) after which diminishing returns takes over
- Stopped Subduction and Antiphase from throwing errors when attempting to displace a creature onto a no-teleport tile
- Fixed Microquasistar throwing errors sometimes when loading levels (it was checking if you had Visages on the map, while the map didn't actually exist yet :p)
Changes for v1.1.1:
- Void Scholar can now appear on NPC's! Fear the Void, adventurer!!!
- Extradiruation is no longer entirely nonfunctional due to the associated hook looking at the incorrect talent ID (OOPSIE DAISY!)
- Extradiruation's breakpoints now occur at every 4 points of Spellpower-Spellsave, and the bonus is no longer hard capped at 5 tiers, instead indefinitely onwards with diminishing returns (similarly to Compressed Space). Its boost per tier has been reduced to compensate (2.4%-4.8% -> 2%-4%)
- Ex Nihilo's base power cap has been slightly increased (50->55)
- Ex Nihilo's HUD overlay now turns red if the power is too low for Visage to trigger
- Ex Nihilo no longer logs blocked damage instances of less than 1
- Visage's duration has been globally increased by 1 (the more aggressive softcap means that the abnormally low starting duration is no longer necessary to moderate numbers)
- Negative Pressure now correctly updates itself when changing levels
- Visage's slow effect's icon now has the correct filename
- Negative Pressure properly safeguards against being updated if the user isn't on the map or if the map isn't defined for whatever reason
Changes for v1.1.2:
- Anti-Singularity's tactical function no longer attempts to modifiy a subset of the table that does not in fact exist
Changes for v1.2.0 "Forbidden Chants Three!":
Logia of Apraxis now blocks all weapon damage regardless of type, in addition to all physical damage regardless of source. Not gonna lie, this is almost purely so that the class has some sort of answer to Shadowblades, who are extremely good at remaining in melee range thus invalidating Dysnomy and Antiposition, and deal a large quantity of Darkness weapon damage (Shadow Veil...) thus invalidating Apraxis, and can inflict Silence and Pin simultaneously just as an added insult, overall making enemies with the class disproportionately deadly.
Logia of Dysnomy now blocks all wildgifts (the vast majority were already blocked on account of being mindpowers, but this catches a few odd talents that aren't, most notably summoned Ritches' flame spit)
Logia of Dysnomy now catches some spells such as Black Ice and Phantasmal Shield that don't flag their damage properly; the method used may or may not be prone to false positives but I think we can all agree that blocking damage you're not supposed to is much less annoying than letting through damage you're not supposed to :)
Logia of Dysnomy now blocks damage from any sort of map effect (the source still has to be at least 2 tiles away)! This is to catch the many, many spells that are not blocked by it on account of creating a map effect as an intermediary. I'm a bit concerned that this might be expanding Dysnomy's scope a bit too much but we shall see how this tastes, as always.
Logia of Dysnomy is now hard-banned from blocking any damage that comes from a weapon attack. Due to how zealous it is about catching things now, this is necessary to check it from potentially overstepping on Apraxis too much :)
Increased the magnitude of Logia Cipher's power/accuracy debuffs by 1/4
Added a message to Logia of Dysnomy's backlash effect from Logia Savant to make it clearer if/when it's actually doing something :)
Logia Enigma no longer reflects damage from dead sources or things that are not actors
Taking damage from friendly targets (most notably yourself) no longer stops you from losing Void Energy
Changes for v1.2.1:
Entering a floor in the Anomalous Sector now flags the player as having been in space (as one would expect), thus allowing you to learn the Aether Permeation prodigy!
Changes for v1.2.2:
Fixed Fillarel not commenting on your class, which I swear worked at some point but as far as I can tell could not possibly have ever worked as it was coded. Mysterious!
Changes for v1.2.3:
Fixed Logia Enigma never working due to checking for an incorrect, nonexistent variable
Liminal Seed now has Unknown as a power source!
Changes for v1.2.4:
Heat Death's numbing effect is now cleared properly when stacking and removing the effectPK ��n ) data/chats/unremarkable-cave-fillarel.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
newChat{ id="welcome?",
text = [[#LIGHT_GREEN#Her assailaint slain, the elf looks upon you with gratitude; however, she seems to notice something displeasing about you, and a glow of hatred starts to simmer behind her crystalline eyes, though as she speaks her words are no less gracious.
#WHITE#Well met, disciple of the abyss. I am in your debt. I know not what errand brings you here, but I advise that you finish it and leave quickly.]],
answers = {
{_t"No need to be hasty. We share a common foe today. Let us be an ally to you against the orcish menace.", jump="help"},
{_t"That's all well and good. I suppose the orcs can do as they please with the mysterious artifact I'm tracking.", jump="help"},
}
}
newChat{ id="help",
text = _t[[#LIGHT_GREEN#You feel uneasy as the elf glares sharply at you for a moment. Then, seemingly satisfied, her eyes soften, though her stance is still guarded.
#WHITE#I sense no deception from you. Very well. I'll send word to High Paladin Aeryn to let you into our stronghold. Know, however, that her wrath is as potent as her patience and generosity.]],
answers = {
{_t"Fair enough. You will not regret this turn of favour. Farewell."},
{_t"Likewise. Our partnership is a rare boon. See to it that it's not wasted."},
}
}
return "welcome?"
PK ���s s data/chats/wormhole-collapse.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
newChat{ id="1",
text = _t[[The wormhole quakes violently and folds in on itself, crushing the captured losgoroths together into a seething mass of aether, tighter and tighter...]],
answers = {
{_t"[Continue]", jump="2"},
}
}
newChat{ id="2",
text = _t[[#VIOLET#Bright violet light sears your eyes as the compacted aether goes critical!!!]],
answers = {
{_t"[Cover your eyes!!!]", action = function(npc, player)
game:onTickEnd(function()
local Map = require "engine.Map"
--scoops up all items currently on the map then drops them on the player's position in the new map :D
local items = {}
for i = 1, game.level.map.w do
for j = 1, game.level.map.h do
local o = 1
local done = nil
while not done do
local obj = game.level.map:getObject(i, j, o)
if obj then
--game.log(obj.name)
items[#items+1] = obj
else
done = true
end
o = o+1
end
while game.level.map:getObject(i, j, 1) do
game.level.map:removeObject(i, j, 1)
end
end
end
--game.log(tostring(player.x) .. " " .. tostring(player.y))
game:changeLevel(2, (rng.table{"trollmire","ruins-kor-pul","scintillating-caves","rhaloren-camp","norgos-lair","heart-gloom"}), {direct_switch=true})
game:onTickEnd(function()
if #items <= 0 then return end
for i = 1, #items do
--game.log(tostring(player.x) .. " " .. tostring(player.y))
game.level.map:addObject(player.x, player.y, items[i])
end
--game.player:describeFloor(game.player.x, game.player.y, true)
end)
local a = require("engine.Astar").new(game.level.map, player)
game:onLevelLoad("wilderness-1", function(zone, level, data)
local list = {}
for i = 0, level.map.w - 1 do for j = 0, level.map.h - 1 do
local idx = i + j * level.map.w
if level.map.map[idx][engine.Map.TERRAIN] and level.map.map[idx][engine.Map.TERRAIN].change_zone == data.from then
list[#list+1] = {i, j}
end
end end
if #list > 0 then
game.player.wild_x, game.player.wild_y = unpack(rng.table(list))
end
end, {from=game.zone.short_name})
local chat = require("engine.Chat").new("voidscholar_class+wormhole-collapse", player, player)
chat:invoke("3")
end)
end},
}
}
newChat{ id="3",
text = _t[[Stars swirl in front of your eyes... no, inside of your head. You don't remember lying down, yet here you are, on the ground.
You lay motionless, your mind empty. Thoughts trickle back into your brain slowly.]],
answers = {
{_t"...", jump="4"},
}
}
newChat{ id="4",
text = _t[[...Yes. When the aether-permeated wormhole exploded, it must have cast vast slipstreams throughout the firmament.
Through some cosmic miracle, you were transported to the surface of Eyal, rather than into the lethal depths of the Void. But how!?
...Then, you notice a strange voratun rod amidst the wreckage around you...]],
answers = {
{_t"[Continue]", jump="5"},
}
}
newChat{ id="5",
text = _t[[It is unlikely that your brethren will realise what happened to you anytime soon, and unlikelier still that they will go looking.
From here on, you will have to make your own way.]],
answers = {
{_t"#VIOLET#To the depths of the Void, and beyond!",
action = function(npc, player)
player:setQuestStatus("voidscholar_class+start-voidscholar", engine.Quest.COMPLETED, "anomalous-sector")
player:grantQuest("starter-zones")
game.player:describeFloor(game.player.x, game.player.y, true)
end,
},
}
}
return "1"
PK *�v5T T data/chats/wryll-return.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
newChat{ id="welcome",
text = [[#LIGHT_GREEN#...?
You find that you are not in Eyal's wilderness as expected. ...In fact, you are back at Terminus Sanctum!
Wryll is standing before you. Their normally-unreadable visage briefly glints with something resembling surprise.
#WHITE#Oh? It's... hmm, whatever your name was. I find that I still don't quite care. Still, to think you're alive... And that your power has grown so...! ...Hmm, yes, I suspect you may become quite interesting yet. Hm, hm, hm...]],
answers = {
{_t"Great Headmaster! How have I returned!? Was this your doing?", jump="how"},
{_t"Well, this is a shock. I thought I'd never get back here. But how...?", jump="how"},
}
}
newChat{ id="how",
text = [[Oh, that... Hm. Shortly after the sudden collapse of the Anomalous Sector, my stargazers started seeing... peculiar... signs coming from Eyal intermittently, similar to a Void gate yet... more refined. Finer. Almost artful.
Isolating the source was somewhat irritating, but far within my power, and once I had, pulling the source back here was mere child's play. Hm, hm, hm.
You, of all things, being the source is... fascinating, but I suppose not entirely illogical.]],
answers = {
{_t"Truly, your wisdom and puissance is beyond compare, Headmaster. Shall I return to my duties?", jump="task"},
{_t"Well, you could have asked first. I was in the middle of something.", jump="task"},
}
}
newChat {id="task",
text = [[Hmm... Yes. I would like you to continue as you were, in fact. I see something in you, or perhaps ABOUT you, that... perhaps wasn't there before, something that currently defies my comprehension. Very, very fascinating...
I shall prepare a spell using this... "Rod of Recall" as a base. With it, you may return to this Sanctum at will, wherever you happen to be.
I look forward to observing your... endeavours... Yes... I have great plans for you...]],
answers = {
{_t"As do I. Thank you for visiting this blessing upon me, and fare thee well."},
{_t"I'll get back to you on that. Farewell."},
}
}
return "welcome"
PK ��)�3
3
data/chats/wryll.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
newChat{ id="welcome",
text = _t[[#LIGHT_GREEN#*A chill runs down your spine as the Headmaster turns their probing gaze towards you.*#WHITE#
What cause do you have to interrupt my meditations?]],
answers = {
{
_t"You summoned me here for a task, Headmaster.",
cond = function(npc, player)
local q = player:hasQuest("voidscholar_class+start-voidscholar")
return q and not q:isCompleted("speak-wryll")
end,
jump = 'task',
},
{_t"Please forgive my insolence. Farewell."},
}
}
newChat { id='task',
text = _t[[#LIGHT_GREEN#*The Headmaster's eyes unfocus for a moment. They then flicker back to you, as though they've only just noticed your presence.*#WHITE#
Is that so? What was your name again, disciple? ...Mmm, I suppose it doesn't matter anyway.
My... hm, our stargazers have relayed to me some fascinating revelations. A peculiar conglomeration of space debris, adrift in the otherwise-pristine Void, some distance away from my... our own Sanctum. Very unusual, wouldn't you agree? There are answers to be pulled from there, surely.]],
answers = {
{
_t"Verily, most wise Headmaster. What would you have me do?",
jump = 'task2',
},
{
_t"I don't like where this is going. Where do I factor in, exactly?",
jump = 'task2',
},
}
}
newChat { id='task2',
text = _t[[Your task, my disciple, is simple, even for one such as... hm.
While I organise a detachment of Voidreachers to investigate the anomalous sector in full, you shall have the honour of handling initial reconnaissance.
I shall ward your body against the ravages of the Deep Void and provide transport to the sector. You shall scout the area and wait for further instructions. I suppose you should try not to die, if it can be at all helped.]],
answers = {
{
_t"Thy will shall be done, Headmaster. It shall be my honour.",
jump = 'sending',
},
{
_t"[Consider refusing; realise this would be unwise.] Very well. Let's get this over with.",
jump = 'sending',
},
}
}
newChat { id='sending',
text = _t [[Yes, yes. It would be in your interest to do this properly, you know. I shan't waste my precious time picking up after your failures, if it comes to that. Farewell.]],
answers = {
{
_t"[Proceed to the Anomalous Sector]",
action = function(npc, player)
player:setQuestStatus("voidscholar_class+start-voidscholar", engine.Quest.COMPLETED, "speak-wryll")
game:changeLevel(1, 'voidscholar_class+anomalous-sector', {direct_switch=true})
end,
},
}
}
return "welcome"
PK �*/�� � data/classdef.lua
local Particles = require "engine.Particles"
newBirthDescriptor{
type = "subclass",
name = "Void Scholar",
locked = function() return true end,
locked_desc = [[The hallowed light of Sun and Moon
cannot forestall a greater doom
Seek out the horrors of the past
to see how stars shall breathe their last]],
desc = {
"Void Scholars both worship and research the power of the infinite celestial void, which they believe holds the key to ultimate dominion over beginnings and endings. Condemned as dangerous heretics by the Sunwall, they live in isolation.",
"Their mastery over the forbidden mysteries of deep space allows the Void Scholars to bend reality to their will, receding unreachably from their foes' blades and spells while extinguishing their existences at the speed of light.",-- Distances are their canvas, and the beings entangled within are their playthings.",
"Their most important stats are: Magic and Cunning",
"#GOLD#Stat modifiers:",
"#LIGHT_BLUE# * +0 Strength, +0 Dexterity, +0 Constitution",
"#LIGHT_BLUE# * +7 Magic, +0 Willpower, +2 Cunning",
"#GOLD#Life per level:#LIGHT_BLUE# -3",
},
--breaks my damn heart doing this but the AI is currently irredeemably bad at playing this class
--not_on_random_boss = true,
--will see what I can do but for now this is a bandaid I have to apply
power_source = {arcane=true},
stats = { mag=7, cun=2 },
--[[birth_example_particles = {
function(actor)
if actor:addShaderAura("master_summoner", "awesomeaura", {time_factor=6200, alpha=0.7, flame_scale=0.8}, "particles_images/naturewings.png") then
elseif core.shader.active(4) then actor:addParticles(Particles.new("shader_ring_rotating", 1, {radius=1.1}, {type="flames", zoom=2, npow=4, time_factor=4000, color1={0.2,0.7,0,1}, color2={0,1,0.3,1}, hide_center=0, xy={self.x, self.y}}))
else actor:addParticles(Particles.new("master_summoner", 1))
end
end,
},]]--
birth_example_particles = "vschol_logia",
talents_types = {
["celestial/vs-logia"]={true, 0.3},
["celestial/vs-void"]={true, 0.3},
["cunning/survival"]={true, 0.0},
["celestial/vs-vacuum"]={true, 0.3},
["celestial/vs-dilation"]={true, 0.3},
["celestial/vs-chaos"]={true, 0.3},
["celestial/vs-collapse"]={true, 0.3},
["celestial/vs-subspace"]={true, 0.3},
["celestial/vs-dissolution"]={true, 0.3},
["celestial/vs-hyperspace"]={false, 0.3},
["celestial/vs-genesis"]={false, 0.3},
["celestial/vs-terminus"]={false, 0.3},
},
talents = {
[ActorTalents.T_VSCHOL_DISRUPT] = 1,
[ActorTalents.T_VSCHOL_FILAMENT] = 1,
[ActorTalents.T_VSCHOL_VOIDWALK] = 1,
[ActorTalents.T_VSCHOL_LOGIA_ACOLYTE] = 1,
[ActorTalents.T_VSCHOL_SIPHON] = 1,
},
copy = {
max_life = 80,
resolvers.auto_equip_filters{
MAINHAND = {type="weapon", subtype="staff"},
OFFHAND = {special=function(e, filter) -- only allow if there is a 1H weapon in MAINHAND
local who = filter._equipping_entity
if who then
local mh = who:getInven(who.INVEN_MAINHAND) mh = mh and mh[1]
if mh and (not mh.slot_forbid or not who:slotForbidCheck(e, who.INVEN_MAINHAND)) then return true end
end
return false
end}
},
resolvers.equipbirth{ id=true,
{type="weapon", subtype="staff", name="elm staff", autoreq=true, ego_chance=-1000},
{type="armor", subtype="cloth", name="linen robe", autoreq=true, ego_chance=-1000},
},
class_start_check = function(self)
if self.descriptor.world == "Maj'Eyal" and not self._forbid_start_override and not self.true_undead then
self.celestial_race_start_quest = self.starting_quest
self.default_wilderness = {"zone-pop", "ruined-gates-of-morning"}
self.starting_zone = "voidscholar_class+terminus-sanctum"
self.starting_quest = "voidscholar_class+start-voidscholar"
self.starting_intro = "voidscholar"
self.faction = "allied-kingdoms"
end
end,
},
copy_add = {
life_rating = -3,
},
}
getBirthDescriptor("class", "Celestial").descriptor_choices.subclass['Void Scholar'] = "allow"PK ����� �
data/lore.luanewLore{
id = "vschol-sector1",
category = "vschol-anomalous-sector",
name = _t"Anomalous Sector (1)",
lore = _t[[Ripples of Void energy gently caress you as you enter the Anomalous Sector. Its power hangs thick all around you, churning the cosmos. Had the Headmaster not warded you against the Void's ravages, you would surely be killed immediately. As it is, you stride across the emptiness as easily as though walking on land.
Chunks of cosmic debris tumble against each other slowly. Something is drawing them in. Whatever it is, it must be deeper within the sector...]],
}
newLore{
id = "vschol-sector2",
category = "vschol-anomalous-sector",
name = _t"Anomalous Sector (2)",
lore = _t[[Losgoroths, void elementals, are omnipresent throughout the cosmos. However, gatherings of this scale only ever congregate around focuses of great arcane power, to feed and reproduce. Moreover, these losgoroths seem degenerated, presumably by the unusual pulses of Void energy in this sector.
As you venture deeper inside, the altered elementals only grow more mutated, taking on peculiar forms. This isn't degeneration. No, this is evolution -- unnaturally wrought by some alien force.]],
}
newLore{
id = "vschol-sector3",
category = "vschol-anomalous-sector",
name = _t"Anomalous Sector (3)",
lore = _t[[The debris is becoming increasingly broken and congested. Powerful Void energies hang thick around you, shimmering threateningly across the wards placed upon you as they flow endlessly inwards, as though being ingested by an unquenchable thirst.
There is a power here that doesn't belong in this universe, feeding upon it like a parasite.]],
}PK �'~ ~ ! data/quests/start-voidscholar.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
name = _t"Beyond Infinity"
desc = function(self, who)
local desc = {}
desc[#desc+1] = _t"The path to ultimate knowledge leads into the unknown, and beyond."
if self:isCompleted("speak-wryll") then
desc[#desc+1] = _t"#LIGHT_GREEN#* You have received your mission from Wryll. You now must see it through to the end.#WHITE#"
--[[if self:isCompleted("return") then
desc[#desc+1] = _t"#LIGHT_GREEN#* You are back in Var'Eyal, the Far East as the people from the west call it.#WHITE#"
else
desc[#desc+1] = _t"#SLATE#* However, you were teleported to a distant land. You must find a way back to the Gates of Morning.#WHITE#"
end]]
if not self:isCompleted("anomalous-sector") then
desc[#desc+1] = _t"#SLATE#* Investigate the Anomalous Sector and report to Wryll with your findings.#WHITE#"
else
desc[#desc+1] = _t"#LIGHT_GREEN#* You neutralised the Ravening Wormhole; however, in the aftermath you were stranded in Maj'Eyal.#WHITE#"
if not self:isCompleted("return") then
desc[#desc+1] = _t"#SLATE#* Survive Eyal's surface and plumb its mysteries in the name of the Void!#WHITE#"
else
desc[#desc+1] = _t"#LIGHT_GREEN#* Through determination and some luck, you managed to find your way back to Terminus Sanctum."
end
end
else
desc[#desc+1] = _t"#SLATE#* You have been summoned by Headmaster Wryll. It would be unwise to keep them waiting.#WHITE#"
end
return table.concat(desc, "\n")
end
on_status_change = function(self, who, status, sub)
if sub then
if self:isCompleted("return") then
who:setQuestStatus(self.id, engine.Quest.DONE)
-- who:grantQuest(who.celestial_race_start_quest)
end
end
end
PK �ϑQ) Q) data/talents/celestial.lua
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-genesis", name = "genesis", description = "Spawn infinite power to repaint the world in your image." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-hyperspace", name = "hyperspace", description = "Twist and reshape reality as you desire." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-terminus", name = "terminus", description = "Dash away transient creations before the Void's infinity." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-vacuum", name = "vacuum", description = "Draw power from the infinite celestial abyss." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-dilation", name = "dilation", description = "Channel Void energy to stretch reality to its limits and beyond." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-chaos", name = "chaos", description = "Cleanse foes in the Void's chaos." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-collapse", name = "collapse", description = "Crush foes into nothing with Void energy." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-subspace", name = "subspace", description = "Ride and steer the currents of the Void which underlie and shape reality." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-dissolution", name = "dissolution", description = "Bleed away your adversaries' power with Void energy." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-void", name = "void", generic = true, description = "Invoke the power of the Void to absorb and negate." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-logia", name = "logia", generic = true, description = "Profess the mysteries of the Void." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="celestial/vs-logia-logia", name = "logia", generic = true, on_mastery_change = function(self, m, tt) if self:knowTalentType("celestial/vs-logia") ~= nil then self.talents_types_mastery[tt] = self.talents_types_mastery["celestial/vs-logia"] end end, description = "Profess the mysteries of the Void." }
newTalent{
name = "Void Pool",
short_name = "VSCHOL_VOID_POOL",
type = {"base/class", 1},
info = "Allows you to have a void energy pool.",
mode = "passive",
hide = "always",
getRegen = function(self, t)
--if it's been 5 turns since we were last in combat we want to override regen entirely
if (self.vschol_void_regenerate or 0) >= 5 then
return -(5 + self.vschol_void * .1)
else
local regen = self.vschol_void_regen or 0
--Null Locus regen and self-damage
if self:isTalentActive(self.T_VSCHOL_NULL_LOCUS) and self.vschol_void_regenerate < 1 then
local tLocus = self:getTalentFromId(self.T_VSCHOL_NULL_LOCUS)
local damage = tLocus.getDamage(self, tLocus)
if (self.life - damage) > self.die_at then
regen = regen + tLocus.getRegen(self, tLocus)
self.life = self.life - damage
end
end
return regen
end
end,
checkHostiles = function(self)
local act
for i = 1, #self.fov.actors_dist do
act = self.fov.actors_dist[i]
if act and self:reactionToward(act) < 0 and self:canSee(act) and self:hasLOS(act.x, act.y, nil, 10) then return true end
end
end,
callbackOnTakeDamage = function(self, t, src)
--reset our hostile count if we take damage from a hostile target regardless of if we see them
if self:reactionToward(src) < 0 then
self.vschol_void_regenerate = 0
end
end,
--handle regeneration/decay in here
--should set the priority very high so it happens before anything else
callbackOnActBase = function(self, t)
if not t.checkHostiles(self) then
--count how long it's been since we last saw a hostile
self.vschol_void_regenerate = (self.vschol_void_regenerate or 0) + 1
else
--reset the counter if we do see one
self.vschol_void_regenerate = 0
end
self:incVschol_void(t.getRegen(self, t))
end,
--handle levelup max growth
--callbackOnLevelup = function(self, t)
--self.max_vschol_void = self.max_vschol_void or 0 + 3
--end,
no_unlearn_last = true,
on_learn = function(self, t)
self.vschol_void = 0
--set it to the appropriate max for our level, accounting for the vanishingly-small chance we already got a max void boost from something
--self.max_vschol_void = (self.max_vschol_void or 0) + (self.level-1) * 3
return true
end,
}
divi_req1 = {
stat = { mag=function(level) return 12 + (level-1) * 2 end },
level = function(level) return 0 + (level-1) end,
}
divi_req2 = {
stat = { mag=function(level) return 20 + (level-1) * 2 end },
level = function(level) return 4 + (level-1) end,
}
divi_req3 = {
stat = { mag=function(level) return 28 + (level-1) * 2 end },
level = function(level) return 8 + (level-1) end,
}
divi_req4 = {
stat = { mag=function(level) return 36 + (level-1) * 2 end },
level = function(level) return 12 + (level-1) end,
}
divi_req5 = {
stat = { mag=function(level) return 44 + (level-1) * 2 end },
level = function(level) return 16 + (level-1) end,
}
divi_req_high1 = {
stat = { mag=function(level) return 22 + (level-1) * 2 end },
level = function(level) return 10 + (level-1) end,
}
divi_req_high2 = {
stat = { mag=function(level) return 30 + (level-1) * 2 end },
level = function(level) return 14 + (level-1) end,
}
divi_req_high3 = {
stat = { mag=function(level) return 38 + (level-1) * 2 end },
level = function(level) return 18 + (level-1) end,
}
divi_req_high4 = {
stat = { mag=function(level) return 46 + (level-1) * 2 end },
level = function(level) return 22 + (level-1) end,
}
divi_req_high5 = {
stat = { mag=function(level) return 54 + (level-1) * 2 end },
level = function(level) return 26 + (level-1) end,
}
function venergy_scale (self, t, base)
return (base + self:combatTalentSpellDamage(t, 5, base))/2
end
load("/data-voidscholar_class/talents/vacuum.lua")
load("/data-voidscholar_class/talents/dilation.lua")
load("/data-voidscholar_class/talents/chaos.lua")
load("/data-voidscholar_class/talents/collapse.lua")
load("/data-voidscholar_class/talents/subspace.lua")
load("/data-voidscholar_class/talents/dissolution.lua")
load("/data-voidscholar_class/talents/hyperspace.lua")
load("/data-voidscholar_class/talents/genesis.lua")
load("/data-voidscholar_class/talents/terminus.lua")
load("/data-voidscholar_class/talents/logia.lua")
load("/data-voidscholar_class/talents/void.lua")
-- NPC spells (used in starting dungeon)
newTalent{
name = "Rock Throw",
short_name = "VSCHOL_ROCK_THROW",
type = {"technique/other",1},
points = 5,
tactical = { ATTACK = { PHYSICAL = 2 } },
range = 6,
proj_speed = 20,
requires_target = true,
target = function(self, t)
local tg = {type="bolt", range=self:getTalentRange(t), talent=t}
tg.display = {display=' ', particle="arrow", particle_args={tile="shockbolt/object/shot_s_iron"}}
return tg
end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 150) end,
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 grids = nil
self:projectile(tg, x, y, DamageType.PHYSICAL, self:spellCrit(t.getDamage(self, t)), function(self, tg, x, y, grids)
game.level.map:particleEmitter(x, y, 1, "archery")
end)
game:playSoundNear(self, "actions/sling")
return true
end,
info = function(self, t)
local damage = t.getDamage(self, t)
return ([[Launches a small rock at the target, dealing %d physical damage.
The damage increases with Spellpower.]]):
tformat(self:damDesc(DamageType.PHYSICAL, damage))
end,
}
newTalent{
name = "Swallow Space",
short_name = "VSCHOL_SWALLOW_SPACE",
type = {"spell/other", 1},
points = 5,
cooldown = 3,
tactical = { ATTACK = 2 },
range = 0,
radius = function(self, t)
return 10
end,
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t}
end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local actors = {}
self:project(tg, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
local dist_sq = (tx - self.x)^2 + (ty - self.y) ^ 2
actors[#actors+1] = {actor=target, dist_sq=dist_sq}
end
end)
--sort actors
table.sort(actors, function(a, b) return a.dist_sq < b.dist_sq end)
--apply pushback
for i = 1, #actors do
if actors[i].actor:canBe("knockback") then
actors[i].actor:pull(self.x, self.y, 2)
end
end
return true
end,
info = function(self, t)
return ([[Draw in the surrounding spacetime, pulling all creatures within radius 10 2 grids towards you.]]):tformat()
end,
}
-- Returns to Terminus Sanctum
newTalent{
short_name = "VSCHOL_TELEPORT_TERMINUS",
image = "talents/teleport_angolwen.png",
name = "Teleport: Terminus Sanctum",
type = {"base/class", 1},
cooldown = 400,
no_npc_use = true,
no_unlearn_last = true,
no_silence=true, is_spell=true,
action = function(self, t)
if not self:canBe("worldport") or self:attr("never_move") then
game.logPlayer(self, "The spell fizzles...")
return
end
local seen = false
-- Check for visible monsters, only see LOS actors, so telepathy wont prevent it
core.fov.calc_circle(self.x, self.y, game.level.map.w, game.level.map.h, 20, function(_, x, y) return game.level.map:opaque(x, y) end, function(_, x, y)
local actor = game.level.map(x, y, game.level.map.ACTOR)
if actor and actor ~= self then seen = true end
end, nil)
if seen then
game.log("There are creatures that could be watching you; you cannot take the risk.")
return
end
self:setEffect(self.EFF_VSCHOL_TELEPORT_TERMINUS, 40, {})
return true
end,
info = _t[[Construct a Void rift leading back to Terminus Sanctum, using the knowledge Wryll extracted from the Rod of Recall. The rift takes 40 turns to fully open.
The Void Scholars must keep their existence a secret; you may not cast this spell or move through the rift if there are witnesses nearby.]]
}PK ����� � data/talents/chaos.lua
newTalent{
name = "Erode",
short_name = "VSCHOL_ERODE",
type = {"celestial/vs-chaos", 1},
require = divi_req1,
points = 5,
range = 6,
radius = 4,
cooldown = 8,
tactical = { ATTACKAREA = {ARCANE = 2}, VSCHOL_VOID = 3 },
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
--mediocre damage, it mostly exists for the Void gain and spellshock
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 150) end,
getGain = function(self, t)
return venergy_scale(self, t, 20)
end,
getAdv = function(self, t)
return self:combatTalentScale(t, 10, 20)
end,
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 dam = self:spellCrit(t.getDamage(self, t))
local hit
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
if target:reactionToward(self) < 0 then
hit = true
end
DamageType:get(DamageType.ARCANE).projector(self, target.x, target.y, DamageType.ARCANE, dam)
target:crossTierEffect(target.EFF_SPELLSHOCKED, self:combatSpellpower() + t.getAdv(self, t))
end
end)
if hit then
game:onTickEnd(function()
self:incVschol_void(t.getGain(self, t))
end)
end
game.level.map:particleEmitter(x, y, 1, "vschol_erode", {radius = 4})
game:playSoundNear(self, "talents/dispel")
return true
end,
info = function(self, t)
return ([[Summon the withering breath of the Void upon a radius-4 area, dealing %d arcane damage to all targets within and degrading their physical forms, possibly Spellshocking them. The Spellshock is applied with %d additional Spellpower.
If at least one hostile target is damaged by this attack, you gain %0.1f Void Energy.
Damage and Void Energy gain improve with Spellpower.]])
:format(self:damDesc(DamageType.ARCANE, t.getDamage(self, t)), t.getAdv(self, t), t.getGain(self, t))
end,
}
newTalent{
name = "Quantum Anisotropy",
short_name = "VSCHOL_QUANTUM_ANISOTROPY",
type = {"celestial/vs-chaos", 2},
require = divi_req2,
points = 5,
mode = "passive",
getSaveReduction = function(self, t)
--linear scaling bc it's a rescaled stat
--return self:getCun() * self:combatTalentScale(t, .12, .24)
return (self:getCun()/100) ^ .75 * self:combatTalentScale(t, 8, 16)
end,
callbackOnDealDamage = function(self, t, val, tgt, dead, death_note)
if not death_note then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
if damtype ~= 'ARCANE' then return end
tgt:setEffect(tgt.EFF_VSCHOL_QUANTUM_ANISOTROPY, 3, {power = t.getSaveReduction(self, t)})
end,
info = function(self, t)
return ([[Your study of the deep cosmos has given you insight into how small inconsistencies bloom and grow over time. Any Arcane damage you deal seeps into tiny imperfections within the target's being, lowering their saves by %0.1f for 3 turns. Reapplying the effect refreshes the duration and stacks the penalty indefinitely, with diminishing returns.
The save penalty scales with your Cunning.]])
:format(t.getSaveReduction(self, t))
end,
}
newTalent{
name = "Destabilise",
short_name = "VSCHOL_DESTABILISE",
type = {"celestial/vs-chaos", 3},
require = divi_req3,
points = 5,
range = 10,
tactical = { ATTACK = {ARCANE = 3} },
requires_target = true,
vschol_void = 10,
cooldown = 8,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 150) end,
getBurstDam = function(self, t) return self:combatTalentSpellDamage(t, 5, 120) end,
getGain = function(self, t)
return venergy_scale(self, t, 12)
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
local _ _, x, y = self:canProject(tg, x, y)
local critMult = self:spellCrit(1)
local dam = t.getDamage(self, t)
self:project({type="hit", talent=t}, x, y, DamageType.ARCANE, dam * critMult)
target:setEffect(target.EFF_VSCHOL_DESTABILISE, 5, {src=self, apply_power = self:combatSpellpower(), damage = t.getBurstDam(self, t) * critMult, gain = t.getGain(self, t)})
game.level.map:particleEmitter(x, y, 1, "vschol_destabilise")
game:playSoundNear(self, "talents/ice")
return true
end,
info = function(self, t)
return ([[Channel the surging energy of the Void into a single target, dealing %d arcane damage, and overpowering them with unstable energy for 5 turns unless they save against your Spellpower. Striking the target with Temporal damage afterwards triggers a violent release of excess energy, dealing %0.1f arcane damage and increasing your Void Energy by %0.1f. This effect can be triggered an indefinite number of times over its duration, with diminishing damage and energy gain.
Damage and Void Energy gain improve with Spellpower.]])
:format(self:damDesc(DamageType.ARCANE, t.getDamage(self, t)), self:damDesc(DamageType.ARCANE, t.getBurstDam(self, t)), t.getGain(self, t))
end,
}
class:bindHook("DamageProjector:final", function (self, data)
if data.src and data.src.x and data.src.isTalentActive and data.target and data.target.combatSpellResist then
if data.type == 'TEMPORAL' and data.src:isTalentActive(data.src.T_VSCHOL_EXTRADIRUATION) then
local tDisent = data.src:getTalentFromId(data.src.T_VSCHOL_EXTRADIRUATION)
local base_boost = tDisent.getBaseBoost(data.src, tDisent)
local diff = data.src:combatSpellpower() - math.max(data.target:combatSpellResist(), 0)
local mult = math.max(math.floor(diff/4), 0)
--game.log(("extradiruation tier: ")..tostring(mult))
if mult > tDisent.max_mult then
local mult_mult = (mult/tDisent.max_mult) ^ .5
mult = tDisent.max_mult * mult_mult
end
--game.log(("final multiplier: ")..tostring(mult))
--mult = math.min(mult, tDisent.max_mult)
local boost = 1 + mult * base_boost
data.dam = data.dam * boost
end
end
return data
end)
newTalent{
name = "Extradiruation",
short_name = "VSCHOL_EXTRADIRUATION",
type = {"celestial/vs-chaos", 4},
require = divi_req4,
points = 5,
mode = "sustained",
sustain_vschol_void = 20,
cooldown = 10,
tactical = { BUFF = 3 },
getBaseBoost = function(self, t)
return self:combatTalentScale(t, .02, .04)
end,
max_mult = 5,
activate = function(self, t)
local ret = {}
game:playSoundNear(self, "talents/teleport")
return ret
end,
deactivate = function(self, t, p)
return true
end,
info = function(self, t)
return ([[Scatter your targets apart into nothingness under the Void's irresistable pull. While sustained, all Temporal damage you deal is increased by %0.1f%% for every 4 points that your Spellpower exceeds the target's Spell Save, up to a total of %0.1f%% increased damage at +20 Spellpower; the boost continues to increase with every 4 points of differece, with geometrically diminishing returns.
The damage boost increases with talent level.]])
:format(t.getBaseBoost(self, t) * 100, t.getBaseBoost(self, t) * 100 * t.max_mult)
end,
}PK ��� data/talents/collapse.lua
newTalent{
name = "Degeneration",
short_name = "VSCHOL_DEGENERATION",
type = {"celestial/vs-collapse", 1},
require = divi_req1,
points = 5,
range = 10,
tactical = { ATTACK = {TEMPORAL = 3} },
requires_target = true,
vschol_void = 5,
cooldown = 3,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 180) end,
getWeakness = function(self, t) return self:combatTalentSpellDamage(t, 200, 12) end,
action = function(self, t)
local tg = {type="hit", range=self:getTalentRange(t)}
local x, y = self:getTarget(tg)
if not x then return nil end
local _ _, x, y = self:canProject(tg, x, y)
local target = game.level.map(x, y, Map.ACTOR)
if target then
local critMult = self:spellCrit(1)
--it's easier to save against because it will be applied a LOT
local stacks = 1
if core.fov.distance(self.x, self.y, target.x, target.y) >= 5 then stacks = 2 end
target:setEffect(target.EFF_VSCHOL_DEGENERATION, 5, {src = self, apply_power = self:combatSpellpower() - 5, power = t.getWeakness(self, t), stacks = stacks})
local dam = t.getDamage(self, t)
self:project({type="hit", talent=t}, x, y, DamageType.TEMPORAL, dam * critMult)
end
game.level.map:particleEmitter(x, y, 1, "gravity_spike", {radius=1, allow=core.shader.allow("distort")})
game:playSoundNear(self, "talents/earth")
return true
end,
info = function(self, t)
return ([[Implode reality around a single target and fuse their atoms together, dealing %d temporal damage and reducing their Temporal resistance by %0.1f%% for 4 turns. The effect can be stacked up to 4 times; if the target is more than 5 tiles away, they suffer an additional stack when hit. The target can save against your Spellpower - 5 to block the effect.
Damage and resistance penalty improve with your Spellpower.]])
:format(self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)), t.getWeakness(self, t))
end,
}
class:bindHook("DamageProjector:final", function (self, data)
if data.src and data.src.x and data.src.isTalentActive then
if data.type == 'TEMPORAL' and data.src:isTalentActive(data.src.T_VSCHOL_COMPRESSED_SPACE) then
local t = data.src:getTalentFromId(data.src.T_VSCHOL_COMPRESSED_SPACE)
local boost = 1 + t.getBoost(data.src, t)
data.dam = data.dam * boost
--if it's the first time this happened this game turn, reduce your void
if not data.src.vschol_compressed_space then
--this is reset within the talent
data.src.vschol_compressed_space = true
game:onTickEnd(function()
data.src:incVschol_void(-data.src.vschol_void * t.drain_perc)
end)
end
end
end
return data
end)
newTalent{
name = "Compressed Space",
short_name = "VSCHOL_COMPRESSED_SPACE",
type = {"celestial/vs-collapse", 2},
require = divi_req2,
points = 5,
mode = "sustained",
sustain_vschol_void = 0,
cooldown = 10,
tactical = { BUFF = 3 },
getBaseBoost = function(self, t)
return self:combatTalentScale(t, .16, .32)
end,
threshold = 30,
drain_perc = .2,
getBoost = function(self, t)
local rate = self.vschol_void / t.threshold
if rate > 1 then rate = rate ^ .5 end
return rate * t.getBaseBoost(self, t)
end,
callbackOnActBase = function(self, t)
self.vschol_compressed_space = nil
end,
iconOverlay = function(self, t, p)
local val = t.getBoost(self, t) or 0
local fnt = "buff_font_small"
--if val >= 1000 then fnt = "buff_font_smaller" end
return tostring(math.ceil(val * 100)) .. "%", fnt
end,
activate = function(self, t)
local ret = {}
game:playSoundNear(self, "talents/earth")
return ret
end,
deactivate = function(self, t, p)
self.vschol_compressed_space = nil
return true
end,
info = function(self, t)
return ([[Distort reality around yourself into a tight knot, allowing you to pour more power into the same space. While sustained, all Temporal damage you do is increased based on your current Void Energy level. The boost increases linearly from 0%% when at empty up to %0.1f%% when at %d Void Energy; it increases further indefinitely as your Void Energy increases, but with geometrically diminishing returns. The first time you deal Temporal damage in a game turn, you lose %d%% of your current Void Energy.
The boost rate increases with talent level.]])
:format(t.getBaseBoost(self, t) * 100, t.threshold, t.drain_perc*100)
end,
}
--handled in the temp effect, the talent def just talks to it
newTalent{
name = "Downshifting",
short_name = "VSCHOL_DOWNSHIFTING",
type = {"celestial/vs-collapse", 3},
require = divi_req3,
points = 5,
mode = "passive",
effnum = 3,
getMult = function(self, t)
return self:combatTalentScale(t, 1.25, 1.50)
end,
info = function(self, t)
return ([[Weigh collapsing targets down with the lingering energy of your combat, crushing them further into nothingness. Your Degeneration debuffs have their resistance reduction multiplied by %d%% whenever the target is suffering from at least %d cleansable detrimental effects. Degeneration itself does not count towards this number.
The multiplier improves with talent level.]])
:format(t.getMult(self, t) * 100, t.effnum)
end,
}
--handled in the temp effect, the talent def just talks to it
newTalent{
name = "Emissive Accretion",
short_name = "VSCHOL_EMISSIVE_ACCRETION",
type = {"celestial/vs-collapse", 4},
require = divi_req4,
points = 5,
mode = "passive",
getChance = function(self, t)
return self:combatTalentLimit(t, 100, 15, 30)/4
end,
info = function(self, t)
return ([[Consume the energy released by your targets as they are crushed by the Void. Any Degeneration effect you apply that has 2 or more stacks will have a %0.2f%% chance per stack of reducing the cooldown of each of your Celestial spells by 1 each turn, to a maximum of %0.1f%% at 4 stacks. The chance is rolled separately for each talent.
Cooldown reduction chance improves with talent level.]])
:format(t.getChance(self, t), t.getChance(self, t)*4)
end,
}PK CO�(- (- data/talents/dilation.lua
--basic spammable beam; your BnB spell
--no cooldown, limited only by your ability to accrue Void Energy
newTalent{
name = "Filament", short_name = "VSCHOL_FILAMENT",
type = {"celestial/vs-dilation", 1},
require = divi_req1,
points = 5,
range = 10,
vschol_void = 10,
target = function(self, t)
return {type="beam", range=self:getTalentRange(t), force_max_range=true, talent=t}
end,
maxRange = 10,
minMult = .5,
requires_target = true,
--very strong for how spammable it is if you space it out properly, otherwise damage is mediocre for the cost
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 320) end,
tactical = function(self, t, target)
local tactic = { attack = { temporal = 3 } }
if target and self.x then
local dist_mod = .5 + .5 * core.fov.distance(self.x, self.y, target.x, target.y) / 10
tactic.attack.temporal = tactic.attack.temporal * dist_mod
end
return tactic
end,--{ ATTACK = { TEMPORAL = 3 } },
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
if self.x == x and self.y == y then return nil end
local critMult = self:spellCrit(1)
local baseDamage = t.getDamage(self, t)
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
local mult = math.min(t.minMult + (1-t.minMult) * ((core.fov.distance(self.x, self.y, tx, ty)-1)/(t.maxRange-1)), 1)
if target then
DamageType:get(DamageType.TEMPORAL).projector(self, target.x, target.y, DamageType.TEMPORAL, baseDamage * mult * critMult)
end
end)
local _ _, x, y = self:canProject(tg, x, y)
game.level.map:particleEmitter(self.x, self.y, tg.radius, "vschol_filament", {tx=x-self.x, ty=y-self.y})
game:playSoundNear(self, "talents/distortion")
return true
end,
info = function(self, t)
local base_dam = self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t))
return ([[Shear and contort space violently in a length-10 line, dealing varying Temporal damage to all targets within based on their distance from you. Damage ranges from %d at range 1 up to %d at range 10 and beyond.
Damage increases with Spellpower.
This spell has no cooldown.]])
:format(base_dam * t.minMult, base_dam)
end,
}
--AoE cone pushback, better at keeping foes away than getting rid of those that are already up in your grill
--damage doesn't scale with range; may change this but it's fairly mediocre damage anyways
newTalent{
name = "Recession",
short_name = "VSCHOL_RECESSION",
type = {"celestial/vs-dilation", 2},
require = divi_req2,
points = 5,
vschol_void = 10,
cooldown = 6,
tactical = function(self, t, target)
local tactic = { attackarea = { temporal = 1 }, disable = 2 }
if target and self.x then
local dist = core.fov.distance(self.x, self.y, target.x, target.y)
--if we're adjacent then it serves no cc purpose
if dist <= 1 then tactic.disable = 0 end
end
return tactic
end,
range = 10,
requires_target = true,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 180) end,
target = function(self, t)
return {type="cone", cone_angle=40, range=0, radius=self:getTalentRange(t), talent=t}
end,
getKBDist = function(self, t)
return math.round(self:combatTalentScale(t, 3, 5.8))
end,
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 critMult = self:spellCrit(1)
local baseDamage = t.getDamage(self, t)
local actors = {}
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
--we save this so we can sort them in descending order of distance so they get pushed neatly
local dist_sq = (tx - self.x)^2 + (ty - self.y) ^ 2
actors[#actors+1] = {actor=target, dist_sq=dist_sq}
DamageType:get(DamageType.TEMPORAL).projector(self, target.x, target.y, DamageType.TEMPORAL, baseDamage * critMult)
end
end)
--sort actors
table.sort(actors, function(a, b) return a.dist_sq > b.dist_sq end)
--apply pushback
for i = 1, #actors do
if core.fov.distance(self.x, self.y, actors[i].actor.x, actors[i].actor.y) > 1 then
if actors[i].actor:canBe("knockback") then
local kb_dist = math.ceil(t.getKBDist(self, t) / 2)
if core.fov.distance(self.x, self.y, actors[i].actor.x, actors[i].actor.y) >= 5 then kb_dist = t.getKBDist(self, t) end
actors[i].actor:knockback(self.x, self.y, kb_dist)
end
end
end
game.level.map:particleEmitter(self.x, self.y, tg.radius, "gravity_breath", {radius=tg.radius, tx=x-self.x, ty=y-self.y, allow=core.shader.allow("distort")})
game:playSoundNear(self, "talents/warp")
return true
end,
info = function(self, t)
return ([[Rapidly flow the space in a 40-degree cone outward, dealing %d temporal damage to all targets within and pushing nonadjacent targets away from yourself; targets less than 4 tiles away are pushed back %d tiles, while farther targets are pushed back %d tiles. This can be resisted via knockback resistance.
Damage increases with Spellpower.]])
:format(self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)), math.ceil(t.getKBDist(self, t) / 2), t.getKBDist(self, t))
end,
}
local function getEmptyAdj (self)
local empty = 0
local emptymap = {{nil, nil, nil}, {nil, nil, nil}, {nil, nil, nil},}
if self.x and game.level and game.level.map then
for i=-1, 1 do
for j = -1, 1 do
if i ~= 0 or j ~= 0 then
local x, y = self.x + i, self.y + j
local inBounds = game.level.map:isBound(x, y)
local actor = inBounds and game.level.map(x, y, Map.ACTOR)
local blocked = inBounds and game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile")
--we exclude visages from the check to avoid a really annoying nonbo
if not (actor and not actor.vschol_visage) and not blocked then
empty = empty+1
emptymap[i+2][j+2] = true
end
end
end
end
end
return empty, emptymap
end
--rewards the player for keeping their space clear
newTalent{
name = "Negative Pressure",
short_name = "VSCHOL_NEGATIVE_PRESSURE",
type = {"celestial/vs-dilation", 3},
require = divi_req3,
points = 5,
mode = "sustained",
sustain_vschol_void = 20,
cooldown = 10,
tactical = { BUFF = 3 },
getMaxIncDam = function(self, t)
return (25 + 15 * (self:combatSpellpower() / 100) ^ .5) * self:combatTalentScale(t, .5, 1)--15 + self:combatTalentSpellDamage(t, 100, 35)
end,
getMaxResPen = function(self, t)
return self:combatTalentLimit(t, 100, 25, 50)
end,
iconOverlay = function(self, t, p)
--local val = t.getBoost(self, t) or 0
--local fnt = "buff_font_small"
--if val >= 1000 then fnt = "buff_font_smaller" end
return p.disp_str or "", buff_font_small
end,
updateTemp = function(self, t, p)
if p.incdam then self:removeTemporaryValue("inc_damage", p.incdam) end
if p.respen then self:removeTemporaryValue("resists_pen", p.respen) end
local empty, emptymap = getEmptyAdj(self)
empty = math.max(empty - 2, 0)
local incdam = empty*(t.getMaxIncDam(self, t) + 10)/6
local respen = empty*(t.getMaxResPen(self, t) + 10)/6
p.incdam = self:addTemporaryValue("inc_damage", { TEMPORAL = incdam })
p.respen = self:addTemporaryValue("resists_pen", { TEMPORAL = respen })
p.disp_str = tostring(math.round(incdam)) .. "%"
if not p.particles then
p.particles = {{}, {}, {}}
end
for i =1,3 do
for j = 1,3 do
if emptymap[i][j] and not p.particles[i][j] then
p.particles[i][j] = self:addParticles(Particles.new("vschol_negative_pressure", 1, {tx = i-2, ty=j-2}))
end
if not emptymap[i][j] and p.particles[i][j] then
self:removeParticles(p.particles[i][j])
p.particles[i][j] = nil
end
end
end
end,
callbackOnAct = function(self, t)
local p = self:isTalentActive(t.id)
if p then
t.updateTemp(self, t, p)
end
end,
callbackOnChangeLevel = function(self, t, dir)
if dir == 'enter' then
t.callbackOnAct(self, t)
end
end,
activate = function(self, t)
local ret = {}
t.updateTemp(self, t, ret)
game:playSoundNear(self, "talents/echo")
return ret
end,
deactivate = function(self, t, p)
if p.incdam then self:removeTemporaryValue("inc_damage", p.incdam) end
if p.respen then self:removeTemporaryValue("resists_pen", p.respen) end
if p.particles then
for i =1,3 do
for j = 1,3 do
if p.particles[i][j] then
self:removeParticles(p.particles[i][j])
end
end
end
end
return true
end,
info = function(self, t)
return ([[Tap into the ambient void power that seeps out from empty spaces, altering your Temporal damage and resistance penetration based on how many unoccupied tiles surround you. The effect ranges from no bonus when 2 or fewer tiles are unoccupied up to +%d%% damage and +%d%% penetration when all tiles are unoccupied.
An "unoccupied" tile is one which does not have a creature in it and can be targeted through.
The maximum Temporal damage bonus improves with Spellpower.]])
:format(t.getMaxIncDam(self, t), t.getMaxResPen(self, t))
end,
}
--Huge radial Temporal nuke
newTalent{
name = "Anti-Singularity",
short_name = "VSCHOL_ANTI_SINGULARITY",
type = {"celestial/vs-dilation", 4},
require = divi_req4,
points = 5,
vschol_void = 25,
cooldown = 20,
tactical = function(self, t, target)
local tactic = { attackarea = { temporal = 4 } }
if target and self.x then
local dist_mod = .25 + .75 * core.fov.distance(self.x, self.y, target.x, target.y) / 10
tactic.attackarea.temporal = tactic.attackarea.temporal * dist_mod
end
return tactic
end,
range = 10,
target = function(self, t)
return {type="ball", range=0, radius=self:getTalentRange(t), selffire=false}
end,
maxRange = 10,
--significantly harsher close-range penalty than Filament
minMult = .2,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 360) end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local critMult = self:spellCrit(1)
local baseDamage = t.getDamage(self, t)
self:project(tg, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
local mult = math.min(t.minMult + (1-t.minMult) * ((core.fov.distance(self.x, self.y, tx, ty)-1)/(t.maxRange-1)), 1)
DamageType:get(DamageType.TEMPORAL).projector(self, target.x, target.y, DamageType.TEMPORAL, baseDamage * critMult * mult)
end
end)
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_antisingularity")
game:playSoundNear(self, "talents/echo")
return true
end,
info = function(self, t)
local base_dam = self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t))
return ([[Overflow the surrounding firmament with void power, stretching reality to its breaking point and blasting away the atoms of all targets within radius 10. Affected targets within take temporal damage based on their distance from you, from %d damage at range 1 up to %d damage at range 10 and beyond.
Damage increases with Spellpower.]])
:format(base_dam * t.minMult, base_dam)
end,
}
PK �̒�" �" data/talents/dissolution.lua
--basic stun, more effective with distance
newTalent{
name = "Equalise", short_name = "VSCHOL_EQUALISE",
type = {"celestial/vs-dissolution", 1},
require = divi_req1,
points = 5,
range = 10,
vschol_void = 10,
cooldown = 12,
target = function(self, t)
return {type="cone", cone_angle=t.getSpread(self, t), range=0, radius=self:getTalentRange(t), talent=t}
end,
maxRange = 6,
requires_target = true,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 140) end,
getSpread = function(self, t)
return self:combatTalentLimit(t, 90, 20, 40)
end,
getMaxDur = function(self, t)
return math.floor(self:combatTalentScale(t, 3, 5.8))
end,
tactical = { DISABLE = 2 },
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
if self.x == x and self.y == y then return nil end
local maxDur = t.getMaxDur(self, t)
local distFactor = (maxDur - 1) / (t.maxRange - 1)
local critMult = self:spellCrit(1)
local dam = critMult * t.getDamage(self, t)
self:project(tg, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
local dist = core.fov.distance(self.x, self.y, tx, ty)
local dur = math.min(maxDur, 1 + (dist - 1)*distFactor)
if target:canBe("stun") then
target:setEffect(target.EFF_STUNNED, dur, {apply_power = self:combatSpellpower()})
else
game.logSeen(target, "%s resists being stunned!", target.name:capitalize())
end
DamageType:get(DamageType.TEMPORAL).projector(self, tx, ty, DamageType.TEMPORAL, dam/2)
DamageType:get(DamageType.TEMPORAL).projector(self, tx, ty, DamageType.ARCANE, dam/2)
end
end)
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_equalise", {tx=x-self.x, ty=y-self.y, section = tg.cone_angle, radius = tg.radius})
game.level.map:particleEmitter(self.x, self.y, tg.radius, "gravity_breath", {radius=tg.radius, tx=x-self.x, ty=y-self.y, allow=core.shader.allow("distort")})
game:playSoundNear(self, "talents/warp")
return true
end,
info = function(self, t)
return ([[Rapidly flow space around targets in a %d-degree cone to scour them of their energy, dealing %d arcane/temporal damage and stunning them unless they save against your Spellpower. The duration of the stun varies based on a target's distance: from 1 turn for adjacent targets up to %d turns for targets at least %d tiles away.
The damage improves with your Spellpower.]])
:format(t.getSpread(self, t), t.getDamage(self, t), t.getMaxDur(self, t), t.maxRange)
end,
}
--barrier, blocks damage and returns it as a DoT
--serves as spike protection
newTalent{
name = "Diffusion Field",
short_name = "VSCHOL_DIFFUSION_FIELD",
type = {"celestial/vs-dissolution", 2},
mode = "sustained",
require = divi_req2,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
tactical = {DEFEND = 3},
no_energy = true,
getBlockRate = function(self, t)
--scaling on this is gonna look pretty wacky but i don't care tbqh
return self:combatTalentLimit(t, 1, .2, .4)
end,
--the higher the length gets the longer the player gets to enjoy effective damage reduction before it's rendered irrelevant by stacking
getLength = function(self, t)
return math.floor(self:combatTalentScale(t, 4, 7.6))
end,
--should trigger after logia and nullify
callbackPriorities={callbackOnHit = 3},
callbackOnHit = function(self, t, cb, src, death_note)
if death_note and death_note.vschol_damage_diffusion then return cb end
local blocked = cb.value * t.getBlockRate(self, t)
if blocked <= 0 then return cb end
cb.value = cb.value - blocked
game:delayedLogDamage(src, self, 0, ("#SLATE#(%d diffused)#LAST#"):format(blocked), false)
local duration = t.getLength(self, t)
local damage = blocked/duration
self:setEffect(self.EFF_VSCHOL_DAMAGE_DIFFUSION, t.getLength(self, t), {power = damage})
return cb
end,
activate = function(self, t)
local ret = {}
game:playSoundNear(self, "talents/spell_generic")
return ret
end,
deactivate = function(self, t, p)
self:removeEffect(self.EFF_VSCHOL_DAMAGE_DIFFUSION)
return true
end,
info = function(self, t)
return ([[Churn and coil space around yourself to disperse the energy of incoming blows, reducing all damage taken by %d%% and returning the absorbed damage over %d turns while sustained. Deactivating this talent releases the remaining stored damage harmlessly as reality around yourself restabilises.
Block rate and the time to return damage increase with talent level.]])
:format(t.getBlockRate(self, t) * 100, t.getLength(self, t))
end,
}
--spending Void Energy grants you a turn advantage
newTalent{
name = "Regress",
short_name = "VSCHOL_REGRESS",
type = {"celestial/vs-dissolution", 3},
require = divi_req3,
points = 5,
mode = "passive",
maxloss = 1,
getTurnLoss = function(self, t)
--somewhat restricted scaling due to the compounding effect of the turn loss
return self:combatTalentLimit(t, 1, .2, .3)
end,
callbackOnTalentPost = function(self, t, ab, ret)
if (util.getval(ab.vschol_void, self, ab) or 0) > 0 and (ab.mode ~= "sustained" or self:isTalentActive(ab.id)) then
local maxdrain = game.energy_to_act * t.getTurnLoss(self, t)
local maxloss = t.maxloss * game.energy_to_act
self:project({type="ball", radius=10, selffire=false, friendlyfire=false}, self.x, self.y, function(tx, ty)
if core.fov.distance(self.x, self.y, tx, ty) >= 5 then
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
if self:checkHit(self:combatSpellpower(), target:combatSpellResist(), 0, 100, 0) then
target.turn_procs.vschol_regress = target.turn_procs.vschol_regress or 0
local drain = math.min(maxdrain, maxloss - target.turn_procs.vschol_regress)
target.turn_procs.vschol_regress = target.turn_procs.vschol_regress + drain
target.energy.value = target.energy.value - drain
game.level.map:particleEmitter(tx, ty, 1, "vschol_regress")
else
game.logSeen(target, "%s resists the regression!", target.name:capitalize())
end
end
end
end)
end
return ret
end,
info = function(self, t)
return ([[Beckon to the Void to devour reality around you whenever you access its gifts. Every time you cast a spell that costs Void Energy, all enemies in radius 10 that are at least 5 tiles away lose %0.1f%% of a turn unless they save against your Spellpower. This talent can drain no more than an entire turn from a target before they can act again.
Turn loss increases with talent level.]])
:format(t.getTurnLoss(self, t) * 100)
end,
}
--improves Diffusion Field by giving a way to dissipate stored damage harmlessly without turning off the talent
newTalent{
name = "Blackbody Radiation",
short_name = "VSCHOL_BLACKBODY_RADIATION",
type = {"celestial/vs-dissolution", 4},
require = divi_req4,
points = 5,
mode = "passive",
getReduction = function(self, t)
return self:combatTalentSpellDamage(t, 5, 30)
end,
callbackOnDealDamage = function(self, t, val, tgt, dead, death_note)
if not death_note then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
if damtype ~= 'TEMPORAL' then return end
if core.fov.distance(self.x, self.y, tgt.x, tgt.y) < 5 then return end
local eff = self:hasEffect(self.EFF_VSCHOL_DAMAGE_DIFFUSION)
if eff then
self.turn_procs.vschol_bbr = (self.turn_procs.vschol_bbr or 0) + 1
local totaldam = eff.power * eff.dur
--game.log(tostring(totaldam))
--total reduction should be getReduction * [targets hit]^.5
--this way hitting multiple targets is profitable but has diminishing returns and will hopefully not get too crazy
local reduction = t.getReduction(self, t) * (self.turn_procs.vschol_bbr ^ .5 - (self.turn_procs.vschol_bbr - 1) ^ .5)
--game.log(tostring(reduction))
totaldam = math.max(totaldam - reduction, 0)
if eff.dur > 0 then
eff.power = totaldam / eff.dur
end
if totaldam <= 0 then
self:removeEffect(self.EFF_VSCHOL_DAMAGE_DIFFUSION)
end
end
end,
info = function(self, t)
return ([[You have immersed yourself in study of the Void's hungering ebbs and flows, learning to touch its very pulse as you tear reality asunder. Any time you deal Temporal damage to a target at least 5 spaces away, a brief Void bridge is created that releases %0.1f stored damage from your Diffusion Field harmlessly into the ether. This effect can be triggered any number of times per turn, with diminishing returns.
The damage dissipation improves with your Spellpower.]])
:format(t.getReduction(self, t))
end,
}PK 7��HI HI data/talents/genesis.lualocal Object = require "engine.Object"
newTalent{
name = "Ex Nihilo",
short_name = "VSCHOL_EX_NIHILO",
type = {"celestial/vs-genesis",1},
mode = "sustained",
require = divi_req_high1,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
tactical = {BUFF = 3},
iconOverlay = function(self, t, p)
local val = t.getPower(self, t) or 0
local fnt = "buff_font_small"
if val >= 1000 then fnt = "buff_font_smaller" end
if val >= t.getMaxPower(self, t) / 4 then
return tostring(math.ceil(val)), fnt
else
return "#LIGHT_RED#" .. tostring(math.ceil(val)), fnt
end
end,
getMaxPower = function(self, t)
return (self:getMag() / 100)^.75 * self:combatTalentScale(t, 25, 55)
end,
getPower = function(self, t)
local ret = self:isTalentActive(t.id)
return ret.power
end,
regenRate = .05,
recalcPower = function(self, t, p)
if p.spellpower then self:removeTemporaryValue("combat_spellpower", p.spellpower) end
p.spellpower = self:addTemporaryValue("combat_spellpower", p.power or 0)
end,
callbackOnActBase = function(self, t)
local p = self:isTalentActive(t.id)
local maxPower = t.getMaxPower(self, t)
p.power = math.min((p.power or 0) + maxPower * t.regenRate, maxPower)
t.recalcPower(self, t, p)
end,
--should trigger after most other DR
callbackPriorities={callbackOnHit = 10},
callbackOnHit = function(self, t, cb, src, death_note)
if src == self then return cb end
local p = self:isTalentActive(t.id)
local save = rng.float(0, p.power or 0)
if save >= cb.value/2 then
if cb.value >= 1 then game:delayedLogDamage(src, self, 0, ("#SLATE#(%d denied)#LAST#"):format(cb.value), false) end
cb.value = 0
else
p.power = math.max(p.power - cb.value*.20, 0)
t.recalcPower(self, t, p)
end
return cb
end,
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = { power = 0 }
t.recalcPower(self, t, ret)
return ret
end,
deactivate = function(self, t, p)
if p.spellpower then self:removeTemporaryValue("combat_spellpower", p.spellpower) end
return true
end,
info = function(self, t)
return ([[Reach beyond the Void to the infinite nonexistence cradling reality, drawing unlimited power from nothing. While sustained, you build a buffer of %0.1f bonus Spellpower per game turn, up to a maximum of %d. Each time you take damage, you roll up to twice the buffer's value and, if the roll is greater than the damage, it is denied and entirely negated. Otherwise, the buffer is reduced by 25%% of the damage.
The maximum Spellpower boost scales with your Magic.]])
:format(t.getMaxPower(self, t) * t.regenRate, t.getMaxPower(self, t))
end,
}
local function countVisage (self)
local number = 0
local array = {}
if #game.level.e_array > 0 then
for i = 1, #game.level.e_array do
local e = game.level.e_array[i]
if e.vschol_visage and e.summoner == self then
number = number+1
array[#array+1] = e
end
end
end
return number, array
end
local function updateMegalo (self)
if self:knowTalent(self.T_VSCHOL_MEGALOTHEOS) then
local t = self:getTalentFromId(self.T_VSCHOL_MEGALOTHEOS)
self:updateTalentPassives(t)
end
end
newTalent{
name = "Visage", short_name = "VSCHOL_VISAGE",
type = {"celestial/vs-genesis", 2},
require = divi_req_high2,
points = 5,
range = 10,
vschol_void = 20,
cooldown = 12,
getDuration = function(self, t) return math.round(self:combatTalentScale(t, 3, 5.8)) end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 40) end,
tactical = { ATTACK = { ARCANE = 3 }, DISABLE = 3 },
falloff_threshold = 5,
createClone = function(self, t, num)
local num = num or 1
-- Find all actors in radius 10 and add them to a table
local tg = {type="ball", radius=self.sight}
local grids = self:project(tg, self.x, self.y, function() end)
local tgts = {}
for x, ys in pairs(grids) do for y, _ in pairs(ys) do
local target = game.level.map(x, y, Map.ACTOR)
if target and self:reactionToward(target) < 0 then tgts[#tgts+1] = target end
end end
local created = 0
if #tgts <= 0 then return created end
for _ = 1,num do
--finds the closest tile to us around each target
local targetgrids = {}
for t = 1, #tgts do
local targetDist
local targetTile
for i = tgts[t].x-1, tgts[t].x+1 do
for j = tgts[t].y-1, tgts[t].y+1 do
if game.level.map:isBound(i, j) and self:canProject({type="hit", nolock=true, range=10}, i, j) and not game.level.map:checkAllEntities(i, j, "block_move") then
local distSQ = (i-self.x)^2 + (j-self.y)^2
if not targetDist or targetDist > distSQ then
targetDist = distSQ
targetTile = {x=i, y=j}
end
end
end
end
if targetTile then
targetgrids[#targetgrids+1] = targetTile
end
end
if #targetgrids > 0 then
local grid = rng.table(targetgrids)
local tx, ty = grid.x, grid.y
if tx then
local Talents = require "engine.interface.ActorTalents"
local NPC = require "mod.class.NPC"
local caster = self
local image = NPC.new{
name = self.name:capitalize() .. "'s visage",
type = "image", subtype = "image",
ai = "summoned", ai_real = nil, ai_state = { talent_in=1, }, ai_target = {actor=nil},
desc = "A mesmerisingly-shimmering image that looks just like "..self.name:capitalize()..", but... hollow.",
image = caster.image,
add_mos = caster.add_mos, -- this is horribly wrong isn't it? seems to work though
shader = "vschol_visage", shader_args = { color = {0.0, 0.4, 0.8}, base = 0.6, time_factor = 1500 },
exp_worth=0,
invulnerable=1,
max_life = caster.max_life,
life = caster.max_life,
combat_armor_hardiness = caster:combatArmorHardiness(),
combat_def = caster:combatDefense(),
combat_armor = caster:combatArmor(),
size_category = caster.size_category,
resists = {},
rank = 1,
life_rating = 0,
--pretty!
lite = 1,
radius=1,
damage=t.getDamage(self, t),
never_anger = true,
vschol_visage = true,
on_act = function(self)
local Map = require "engine.Map"
local DamageType = require "engine.DamageType"
self.projecting = true -- simplest way to indicate that this damage should not be amplified by the in creeping dark bonus
self.summoner.__project_source = self -- intermediate projector source
self.summoner:project({type="ball", start_x = self.x, start_y=self.y, radius=self.radius, talent=self.summoner:getTalentFromId(self.summoner.T_VSCHOL_VISAGE), friendlyfire = false}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target and self.summoner:checkHit(self.summoner:combatSpellpower() - 10, target:combatSpellResist(), 0, 100, 0) then
DamageType:get(DamageType.ARCANE).projector(self.summoner, tx, ty, DamageType.ARCANE, self.damage)
target:setEffect(target.EFF_VSCHOL_ENRAPTURED, 1, {power=.2})
end
end)
self.summoner.__project_source = nil
self.projecting = false
end,
on_die = function(self)
local t = self.summoner:getTalentFromId(self.summoner.T_VSCHOL_MEGALOTHEOS)
self.summoner:updateTalentPassives(t)
end,
faction = caster.faction,
summoner = caster,
summon_time=t.getDuration(self, t),
no_breath = 1,
remove_from_party_on_death = true,
}
image:resolve()
game.zone:addEntity(game.level, image, "actor", tx, ty)
image.energy.value = game.energy_to_act
created = created+1
game.level.map:particleEmitter(tx, ty, 1, "vschol_visage_summon")
updateMegalo(self)
end
end
end
return created
end,
callbackOnCrit = function(self, t, type)
if type ~= 'spell' then return end
local t_XN = self:getTalentFromId(self.T_VSCHOL_EX_NIHILO)
local XN = self:isTalentActive(self.T_VSCHOL_EX_NIHILO)
if not XN or XN.power < t_XN.getMaxPower(self, t_XN) * .25 then return end
--if rng.percent(50) and countVisage(self) > 10 then return end
local visageNum = countVisage(self)
if visageNum >= t.falloff_threshold then
local chance = 100 * (t.falloff_threshold / 2) / visageNum
if not rng.percent(chance) then return end
end
t.createClone(self, t)
end,
on_pre_use = function(self, t, silent)
local t_XN = self:getTalentFromId(self.T_VSCHOL_EX_NIHILO)
local XN = self:isTalentActive(self.T_VSCHOL_EX_NIHILO)
if not XN or XN.power < t_XN.getMaxPower(self, t_XN) * .25 then
if not silent then
game.logPlayer(self, "Ex Nihilo must be at least 25%% of its capacity to use this talent.")
end
return false
end
return true
end,
action = function(self, t)
local created = t.createClone(self, t, 2)
if created == 0 then return end
game:playSoundNear(self, "talents/distortion")
return true
end,
info = function(self, t)
return ([[The infinite power you have brought forth allows you to create any form you desire, reshaping the world in your own image. Whenever one of your spells goes critical while Ex Nihilo is active and at least 25%% capacity, you manifest a hollow clone of yourself adjacent to a random enemy in radius 10. The clone lasts %d turns, is invulnerable and does not act, and deals %0.1f arcane damage to nearby enemies and slows them by 20%% each turn unless they save against your Spellpower - 10.
You may activate this talent to immediately spawn 2 clones. If you have at least %d clones active, criticals will only spawn clones 50%% of the time; the chance further decreases proportionally as the number of clones increases.
The clones' radial damage improves with your Spellpower.]])
:format(t.getDuration(self, t), self:damDesc(DamageType.ARCANE, t.getDamage(self, t)), t.falloff_threshold)
end,
}
--counts how many Visages the actor has on the current map
--@return a count of the visages, an array containing all of the visages owned
--probably a less resource intensive way to do this but i have a headache so fug it
local function countVisage (self)
local number = 0
local array = {}
if game.level and #game.level.e_array > 0 then
for i = 1, #game.level.e_array do
local e = game.level.e_array[i]
if e.vschol_visage and e.summoner == self then
number = number+1
array[#array+1] = e
end
end
end
return number, array
end
newTalent{
name = "Microquasistar",
short_name = "VSCHOL_MICROQUASISTAR",
type = {"celestial/vs-genesis", 3},
require = divi_req_high3,
points = 5,
range = 6,
radius = 4,
vschol_void = 10,
cooldown = 4,
--use this whenever possible! :D
tactical = { ATTACKAREA = {TEMPORAL = 10} },
--couldnt get npcs to use this for whatever reason so lets just make them not waste points in it
no_npc_use = true,
requires_target = true,
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent = t}
end,
getDur = function(self, t) return math.round(self:combatTalentScale(t, 4, 6.8)) end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 80) end,
on_pre_use = function(self, t, silent)
local num = countVisage(self)
if num < 2 then
if not silent then
game.logPlayer(self, "You must have 2 Visage clones active to use this talent.")
end
return false
end
return true
end,
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 _, visages = countVisage(self)
--if the tile we pick is occupied we find the nearest unoccupied tile we can project to
local blocked = game.level.map:checkAllEntities(x, y, "block_move")
--kludge, if we're targeting one of our own visages we assume we can assume its safe to just put the guy there :)
if blocked then
for i=1, #visages do
if visages[i].x == x and visages[i].y == y then blocked = false end
end
end
if blocked then
local targetDist
local targetTile
for i = x-1, x+1 do
for j = y-1, y+1 do
local blocked = game.level.map:isBound(i, j) and game.level.map:checkAllEntities(i, j, "block_move")
if game.level.map:isBound(i, j) and self:canProject({type="hit", nolock=true, range=self:getTalentRange(t)}, i, j) and not blocked then
local distSQ = (i-self.x)^2 + (j-self.y)^2
if not targetDist or targetDist > distSQ then
targetDist = distSQ
targetTile = {x=i, y=j}
end
end
end
end
if not targetTile then return end
x, y = targetTile.x, targetTile.y
end
--if we're trying to place the star on a visage we prioritise getting rid of the one we're dropping it on first :)
table.sort(visages, function(a, b)
local a_match = (a.x == x and a.y == y)
local b_match = (b.x == x and b.y == y)
if a_match then return true end
if b_match then return false end
return a.summon_time < b.summon_time
end)
for i=1, 2 do
game.level.map:particleEmitter(visages[i].x, visages[i].y, 1, "vschol_visage_summon")
visages[i]:disappear()
end
local e = Object.new{
name = self.name:capitalize() .. "'s Microquasistar",
block_sight = false,
block_move = true,
canAct = false,
x = x, y = y,
duration = t.getDur(self, t),
radius = t.radius,
lite = 6,
damage = t.getDamage(self, t),
summoner = self,
summoner_gain_exp = true,
target=tg,
act = function(self)
local Map = require "engine.Map"
local DamageType = require "engine.DamageType"
self:useEnergy()
if self.duration <= 0 then
-- remove
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
if self.particles2 then game.level.map:removeParticleEmitter(self.particles2) end
if self.particles3 then game.level.map:removeParticleEmitter(self.particles3) end
game.level.map:remove(self.x, self.y, Map.TERRAIN+3)
game.level:removeEntity(self, true)
--self.creepingDark = nil
game.level.map:scheduleRedisplay()
else
local actors = {}
local critMult = nil
local Map = require "engine.Map"
local DamageType = require "engine.DamageType"
self.projecting = true -- simplest way to indicate that this damage should not be amplified by the in creeping dark bonus
self.summoner.__project_source = self -- intermediate projector source
self.summoner:project({type="ball", start_x = self.x, start_y=self.y, radius=self.radius, talent=self.summoner:getTalentFromId(self.summoner.T_VSCHOL_MICROQUASISTAR)}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
critMult = critMult or self.summoner:spellCrit(1)
DamageType:get(DamageType.ARCANE).projector(self.summoner, tx, ty, DamageType.ARCANE, self.damage/2 * critMult)
DamageType:get(DamageType.TEMPORAL).projector(self.summoner, tx, ty, DamageType.TEMPORAL, self.damage/2 * critMult)
local dist_sq = (tx - self.x)^2 + (ty - self.y) ^ 2
actors[#actors+1] = {actor=target, dist_sq=dist_sq}
end
end)
self.summoner.__project_source = nil
self.projecting = false
--sort actors
table.sort(actors, function(a, b) return a.dist_sq < b.dist_sq end)
--apply pushback
for i = 1, #actors do
if actors[i].actor:canBe("knockback") then
actors[i].actor:pull(self.x, self.y, 1)
end
end
end
self.duration = self.duration-1
end,
}
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN+3, e)
e.energy.value = game.energy_to_act
local particle = Particles.new("circle", 1, {img="mrfrog/quasistar", speed=8, appear=8, radius = .6, a = 255, appear_size = 0})
particle.zdepth = 18
e.particles = game.level.map:addParticleEmitter(particle, x, y)
local particle2 = Particles.new("circle", 1, {shader=true, img="mrfrog/quasistar", speed=12, appear=8, radius = 1.2, a = 64, appear_size = 0})
particle2.zdepth = 19
e.particles2 = game.level.map:addParticleEmitter(particle2, x, y)
local particle3 = Particles.new("vschol_quasistar", 1, {radius = 4})
particle3.zdepth = 17
e.particles3 = game.level.map:addParticleEmitter(particle3, x, y)
--so we can find if its on a square easily
e.vschol_quasistar = e
game:playSoundNear(self, "talents/lightning_loud")
return true
end,
info = function(self, t)
local damage = t.getDamage(self, t)
return ([[Crush together the raw progenitive force of 2 Visage clones to create a self-sustaining miniature star at the targeted location. The star's tremendous gravity and energy output blasts all targets in radius 4 for %d arcane/temporal damage each turn and pulls them in towards the core. After %d turns, conventional physics reasserts itself and the star dissipates harmlessly.
Damage increases with Spellpower.
The stars' damage crits separately each turn and can summon a Visage if it does so.]])
:format(self:damDesc(DamageType.ARCANE, damage/2) + self:damDesc(DamageType.TEMPORAL, damage/2), t.getDur(self, t))
end,
}
newTalent{
name = "Megalotheos",
short_name = "VSCHOL_MEGALOTHEOS",
type = {"celestial/vs-genesis", 4},
require = divi_req_high4,
points = 5,
mode = 'passive',
getCritChance = function(self, t)
return self:combatTalentScale(t, 2.5, 5)
end,
getCritMult = function(self, t)
return self:combatTalentScale(t, 5, 10)
end,
callbackOnChangeLevel = function(self, t, mode)
if mode == "enter" then
self:updateTalentPassives(t)
end
end,
passives = function(self, t, p)
local num = countVisage(self)
self:talentTemporaryValue(p, "combat_spellcrit", t.getCritChance(self, t) * num)
self:talentTemporaryValue(p, "combat_critical_power", t.getCritMult(self, t) * num)
end,
info = function(self, t)
return ([[As you dominate reallty with your ego, your power over it increases yet further. Each Visage clone on the map now grants you %0.2f%% greater spell critical chance and %0.2f%% greater critical power, up to a maximum of 20 clones.
Bonuses increase with talent level.]])
:format(t.getCritChance(self, t), t.getCritMult(self, t))
end,
}PK ��H��C �C data/talents/hyperspace.lualocal Object = require "engine.Object"
--Passive space control that makes it difficult for foes to approach you
--serves as a buffer to keep the space immediately around you clean
newTalent{
name = "Hypersphere",
short_name = "VSCHOL_HYPERSPHERE",
type = {"celestial/vs-hyperspace",1},
mode = "sustained",
require = divi_req_high1,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
tactical = {BUFF = 3},
radius = function(self, t)
return math.min(math.floor(self:combatTalentScale(t, 3, 4.75)), 5)
end,
target = function(self, t)
return {type="ball", range=0, radius=self:getTalentRadius(t), selffire=false}
end,
getChance = function(self, t)
return self:combatTalentLimit(t, 100, 33, 50)
end,
getDamage = function(self, t)
return self:combatTalentSpellDamage(t, 5, 50)
end,
callbackOnActEnd = function(self, t)
local actors = {}
local desttiles = {}
self:project({type="ball", range=0, radius=self:getTalentRadius(t), friendlyfire=false}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
--if target and rng.percent(t.getChance(self, t) * (core.fov.distance(self.x, self.y, tx, ty) <= 1 and .5 or 1)) then
if target and core.fov.distance(self.x, self.y, tx, ty) > 1 and rng.percent(t.getChance(self, t)) then
--like most of Void Scholar's distance control options, it fails if the opponent's already breached your space
--you can counteract this a bit by simply moving away though :)
if target:canBe("teleport") and self:checkHit(self:combatSpellpower() - 10, target:combatSpellResist() + (target:attr("continuum_destabilization") or 0), 0, 100, 0) then
actors[#actors+1] = target
else
game.logSeen(target, "%s resists the hypersphere!", target.name:capitalize())
end
end
end)
self:project({type="ball", range=0, radius=self:getTalentRadius(t) + 2}, self.x, self.y, function(tx, ty)
if core.fov.distance(self.x, self.y, tx, ty) > self:getTalentRadius(t) then
desttiles[#desttiles+1] = {x=tx, y=ty}
end
end)
--game.log(tostring(#actors))
--game.log(tostring(#desttiles))
for i = 1, #actors do
if #actors <= 0 or #desttiles <= 0 then break end
local actor = rng.tableRemove(actors)
--game.log(actor.name)
local angle = math.atan2(actor.y-self.y, actor.x-self.x)
--this rigamarole is so that it will prioritise placing the creature at the edge of the sphere colinearly with its position relative to the center
--thus keeping its direction consistent to minimise awkward situations where a creature is suddenly on the opposite side of the player as it was :)
local cX, cY = actor.x, actor.y
local d_cX, d_cY = cX - self.x, cY - self.y
local c_dist = ((d_cX ^ 2) + (d_cY ^ 2)) ^ .5
d_cX = d_cX * (self:getTalentRadius(t)/c_dist)
d_cY = d_cY * (self:getTalentRadius(t)/c_dist)
cX = self.x + d_cX
cY = self.y + d_cY
--note that it will still put enemies on the opposite side if theres no other option
--since the primary purpose of this talent IS to help Void Scholar keep its surroundings clear
--desttile is an index number, not the tile itself!
local desttile = nil
local closestDistSQ = nil
for d = 1, #desttiles do
if not desttiles[d].taken and actor:canMove(desttiles[d].x, desttiles[d].y) then
local distSQ = (desttiles[d].x - cX)^2 + (desttiles[d].y - cY) ^ 2
if not closestDistSQ or closestDistSQ > distSQ then
closestDistSQ = distSQ
desttile = d
end
end
end
local critMult = nil
if desttile then
desttiles[desttile].taken = true;
game.level.map:particleEmitter(actor.x, actor.y, 1, "vschol_hypersphere_displace")
game.level.map:particleEmitter(desttiles[desttile].x, desttiles[desttile].y, 1, "vschol_hypersphere_displace")
actor:move(desttiles[desttile].x, desttiles[desttile].y, true)
critMult = critMult or self:spellCrit(1)
DamageType:get(DamageType.TEMPORAL).projector(self, actor.x, actor.y, DamageType.TEMPORAL, t.getDamage(self, t) * critMult)
end
end
end,
--the ai keeps deactivating this for whatever reason so lets specifically tell them not to :V
on_pre_use_ai = function (self, t, silent, fake)
if self:isTalentActive(t.id) then return false end
return true
end,
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic")
local ret = {}
ret.particle = self:addParticles(Particles.new("vschol_hypersphere", 1, {radius = self:getTalentRadius(t)}))
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
return true
end,
info = function(self, t)
return ([[Twist and contort space to create a radius-%d zone around you into which movement bends back outwards on itself. While sustained, nonadjacent enemies in this radius have a %0.1f%% chance of being displaced out of it every time you finish a turn, suffering %d temporal damage in the process, unless they save against your Spellpower - 10.
Teleport chance improves with talent level.]])
:format(self:getTalentRadius(t), t.getChance(self, t), self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)))
end,
}
--extreme AoE damage and a bit of CC, but delayed and with great potential for self harm
--rewards the player for consistently playing field control well
newTalent{
name = "Hypercube",
short_name = "VSCHOL_HYPERCUBE",
type = {"celestial/vs-hyperspace", 2},
require = divi_req_high2,
points = 5,
range = 10,
radius = 6,
vschol_void = 20,
cooldown = 16,
tactical = { ATTACKAREA = {TEMPORAL = 5} },
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent = t}
end,
delay = 3,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 360) end,
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)
--if the tile we pick is occupied we find the nearest unoccupied tile we can project to
if game.level.map:checkAllEntities(x, y, "block_move") then
local targetDist
local targetTile
for i = x-1, x+1 do
for j = y-1, y+1 do
if game.level.map:isBound(i, j) and self:canProject({type="hit", nolock=true, range=10}, i, j) and not game.level.map:checkAllEntities(i, j, "block_move") then
local distSQ = (i-self.x)^2 + (j-self.y)^2
if not targetDist or targetDist > distSQ then
targetDist = distSQ
targetTile = {x=i, y=j}
end
end
end
end
if not targetTile then return end
x, y = targetTile.x, targetTile.y
end
local e = Object.new{
name = self.name:capitalize() .. "'s hypercube",
block_sight = false,
block_move = true,
canAct = false,
x = x, y = y,
duration = t.delay,
radius = t.radius,
damage = self:spellCrit(t.getDamage(self, t)),
summoner = self,
summoner_gain_exp = true,
target=tg,
act = function(self)
local Map = require "engine.Map"
local DamageType = require "engine.DamageType"
self:useEnergy()
if self.duration <= 0 then
self.projecting = true -- simplest way to indicate that this damage should not be amplified by the in creeping dark bonus
self.summoner.__project_source = self -- intermediate projector source
self.summoner:project({type="ball", start_x = self.x, start_y=self.y, radius=self.radius, talent=self.summoner:getTalentFromId(self.summoner.T_VSCHOL_HYPERCUBE)}, self.x, self.y, engine.DamageType.TEMPORAL, self.damage)
self.summoner.__project_source = nil
self.projecting = false
-- remove
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
if self.particles2 then game.level.map:removeParticleEmitter(self.particles2) end
game.level.map:remove(self.x, self.y, Map.TERRAIN+3)
game.level:removeEntity(self, true)
--self.creepingDark = nil
game.level.map:scheduleRedisplay()
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_hypercube_blast")
game:playSoundNear(self, "talents/lightning")
else
if self.event_horizon then
self.summoner:project({type="ball", start_x = self.x, start_y=self.y, radius=self.event_horizon}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
target:setEffect(target.EFF_VSCHOL_EVENT_HORIZON, 1, {x = self.x, y = self.y})
end
end)
end
end
self.duration = self.duration-1
end,
}
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN+3, e)
e.energy.value = game.energy_to_act
local particle = Particles.new("wormhole", 1, {image="npc/voidscholar/hypercube", speed=0})
particle.zdepth = 6
e.particles = game.level.map:addParticleEmitter(particle, x, y)
--so we can find if its on a square easily
e.vschol_hypercube = e
game.level.map:particleEmitter(x, y, 1, "gravity_spike", {radius=10, allow=core.shader.allow("distort")})
game.level.map:particleEmitter(x, y, 1, "gravity_spike", {radius=5, allow=core.shader.allow("distort")})
game.level.map:particleEmitter(x, y, 1, "gravity_spike", {radius=2, allow=core.shader.allow("distort")})
local actors = {}
self:project({type="ball", radius=8}, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
--we save this so we can sort them in ascending order of distance so they get pushed neatly
local dist_sq = (tx - x)^2 + (ty - y) ^ 2
actors[#actors+1] = {actor=target, dist_sq=dist_sq}
end
end)
--sort actors
table.sort(actors, function(a, b) return a.dist_sq < b.dist_sq end)
--apply pushback
for i = 1, #actors do
if actors[i].actor:canBe("knockback") then
actors[i].actor:pull(x, y, 2)
end
end
if self:knowTalent(self.T_VSCHOL_EVENT_HORIZON) then
local t_EH = self:getTalentFromId(self.T_VSCHOL_EVENT_HORIZON)
e.event_horizon = 3
e.event_horizon_chance = t_EH.getChance(self, t_EH)
self:project({type="ball", radius=3}, x, y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
target:setEffect(target.EFF_VSCHOL_EVENT_HORIZON, 1, {x = e.x, y = e.y})
end
end)
end
if e.event_horizon then
local particle2 = Particles.new("vschol_hypersphere", 1, {radius = 3})
e.particles2 = game.level.map:addParticleEmitter(particle2, x, y)
end
game:playSoundNear(self, "talents/earth")
return true
end,
info = function(self, t)
return ([[Wind a radius-8 space into a tightly-compacted Void cube at the target location, drawing in all creatures in the area towards it by 2 tiles. After %d turns, the cube will destabilise and explode in a cataclysmic spatial wave, dealing %d temporal damage to all targets (including yourself) in radius %d.
Damage improves with Spellpower.]])
:format(t.delay, self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)), t.radius)
end,
}
--potent ejection tool; reliable, nondirectional, keeps enemies neatly gathered, and unlike most of Void Scholar's space control actually works on adjacent foes
--the persistent Void Energy penalty afterwards discourages the player from using it carelessly
newTalent{
name = "Transposition",
short_name = "VSCHOL_TRANSPOSITION",
type = {"celestial/vs-hyperspace", 3},
require = divi_req_high3,
points = 5,
range = 8,
radius = function(self, t)
if self:knowTalent(self.T_VSCHOL_HYPERSPHERE) then
return self:getTalentRadius(self:getTalentFromId(self.T_VSCHOL_HYPERSPHERE))
end
return 3
end,
vschol_void = 10,
cooldown = 8,
tactical = { DISABLE = 3, ATTACKAREA = { TEMPORAL = 3 } },
target = function(self, t)
return {type="ball", nolock=true, range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 200) end,
on_pre_use_ai = function (self, t, silent, fake)
local target = self.ai_target and self.ai_target.actor
if target and self.x then
local x, y = self:aiSeeTargetPos(aitarget)
if not self:hasLOS(x, y, nil, self:getTalentRadius(t)) then return false end
end
return true
end,
on_pre_use = function(self, t, silent)
local active = self:isTalentActive(self.T_VSCHOL_HYPERSPHERE)
if not active then
if not silent then
game.logPlayer(self, "You must have Hypersphere sustained to use this talent.")
end
return false
end
return true
end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local x, y
local x, y
if self:playerControlled() then
x, y = self:getTarget(tg)
else
--ais should try to move the player as far away as possible
local aitarget = self.ai_target and self.ai_target.actor
if aitarget and aitarget.x and core.fov.distance(self.x, self.y, aitarget.x, aitarget.y) <= self:getTalentRadius(t) then
local dist = core.fov.distance(self.x, self.y, aitarget.x, aitarget.y)
local distFactor = self:getTalentRange(t) / dist
local dx, dy = aitarget.x - self.x, aitarget.y - self.y
dx = math.round(dx * distFactor)
dx = math.round(dy * distFactor)
x, y = self.x + dx, self.y + dy
end
end
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))
local actors = {}
local desttiles = {}
self:project({type="ball", range=0, radius=self:getTalentRadius(t), selffire=false}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
if target:canBe("teleport") then
actors[#actors+1] = target
else
game.logSeen(target, "%s resists the space warp!", target.name:capitalize())
end
DamageType:get(DamageType.TEMPORAL).projector(self, tx, ty, DamageType.TEMPORAL, damage)
end
end)
self:project(tg, x, y, function(tx, ty)
desttiles[#desttiles+1] = {x=tx, y=ty}
end)
for i = 1, #actors do
if #actors <= 0 or #desttiles <= 0 then break end
local actor = rng.tableRemove(actors)
--game.log(actor.name)
local angle = math.atan2(actor.y-self.y, actor.x-self.x)
local cX, cY = (actor.x-self.x)+x, (actor.y-self.y)+y
--desttile is an index number, not the tile itself!
local desttile = nil
local closestDistSQ = nil
for d = 1, #desttiles do
if not desttiles[d].taken and actor:canMove(desttiles[d].x, desttiles[d].y) then
local distSQ = (desttiles[d].x - cX)^2 + (desttiles[d].y - cY) ^ 2
if not closestDistSQ or closestDistSQ > distSQ then
closestDistSQ = distSQ
desttile = d
end
end
end
if desttile then
desttiles[desttile].taken = true;
game.level.map:particleEmitter(actor.x, actor.y, 1, "vschol_hypersphere_displace")
game.level.map:particleEmitter(desttiles[desttile].x, desttiles[desttile].y, 1, "vschol_hypersphere_displace")
actor:move(desttiles[desttile].x, desttiles[desttile].y, true)
end
end
self:setEffect(self.EFF_VSCHOL_VOID_DISRUPTION, 5, {power = .2})
game.level.map:particleEmitter(x, y, 1, "gravity_spike", {radius=3, allow=core.shader.allow("distort")})
game.level.map:particleEmitter(self.x, self.y, 1, "gravity_spike", {radius=3, allow=core.shader.allow("distort")})
game:playSoundNear(self, "talents/warp")
return true
end,
info = function(self, t)
return ([[Punch a hole through the Void, linking a targeted radius-%d space to your Hypersphere. All creatures within your Hypersphere take %d temporal damage and are moved to the targeted area. Afterwards, the lingering damage to the firmament bleeds you of 20%% of your Void Energy per turn for 5 turns.
Damage improves with Spellpower.]])
:format(self:getTalentRadius(t), self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)))
end,
}
--adds further CC value to Hypercube and additionally makes it much more difficult for foes to escape its blast
--note that this can backfire! Please cube responsibly :)
newTalent{
name = "Event Horizon",
short_name = "VSCHOL_EVENT_HORIZON",
type = {"celestial/vs-hyperspace", 4},
require = divi_req_high4,
points = 5,
mode = "passive",
--taking your entire lifebar because rng decided that you won't get away from the bomb is Not Fun
no_npc_use = true,
getChance = function(self, t)
return self:combatTalentLimit(t, 100, 20, 60)
end,
info = function(self, t)
return ([[You put your full mastery of the Void's twisted power behind you as you resculpt reality like putty, giving your Hypercubes such mass that they bend space backwards around themselves in radius 3. Each time a creature in this radius moves, they have a %d%% chance of arriving adjacent to the cube instead of their expected destination unless they save against your Spellpower.
Teleport chance improves with talent level.]])
:format(t.getChance(self, t))
end,
}PK ��h$%E %E data/talents/logia.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
--keeping these in one spot for legibility since they function as a unit
local function tactics_apraxis (self, t, target)
if not target then return { buff = 1 } end
if core.fov.distance(self.x, self.y, target.x, target.y) <= 1 then
return { buff = 10 }
else
return { buff = 1 }
end
end
local function tactics_dysnomy (self, t, target)
if not target then return { buff = 1 } end
local dist = core.fov.distance(self.x, self.y, target.x, target.y)
if dist < 6 and dist > 1 then
return { buff = 10 }
else
return { buff = 1 }
end
end
local function tactics_antiposition (self, t, target)
--start with antiposition since turning it off after the player approaches is least harmful
if not target then return { buff = 2 } end
if core.fov.distance(self.x, self.y, target.x, target.y) >= 6 then
return { buff = 10 }
else
return { buff = 2 }
end
end
local function doLogiaReflect(self, damage, target)
if not self:knowTalent(self.T_VSCHOL_LOGIA_ENIGMA) then return end
if not target or target.dead or not target.__is_actor then return end
if self.turn_procs._logia_reflect then return end
local tEnigma = self:getTalentFromId(self.T_VSCHOL_LOGIA_ENIGMA)
local reflected = damage * tEnigma.getMultiplier(self, tEnigma)
self.turn_procs._logia_reflect = true
--i only noticed just now that this seems to prevent it from projecting through walls? probably fine though
self:project({type="hit", talent=tEnigma, selffire = false}, target.x, target.y, DamageType.ARCANE, reflected)
self.turn_procs._logia_reflect = nil
end
newTalent{
name = "Logia of Apraxis",
short_name = "VSCHOL_LOGIA_OF_APRAXIS",
type = {"celestial/vs-logia-logia",1},
mode = "sustained",
hide = true,
require = divi_req1,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
no_energy = true,
dont_provide_pool = true,
tactical = tactics_apraxis,
getDamReduce = function(self, t)
return math.round(self:combatTalentSpellDamage(t, 5, 60))
end,
callbackOnHit = function(self, t, cb, src, death_note)
if not death_note then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
--[[do
getfenv(0).vschol_debug = getfenv(0).vschol_debug or {}
getfenv(0).vschol_debug.apraxis_hit = getfenv(0).vschol_debug.apraxis_hit or {}
table.insert(getfenv(0).vschol_debug.apraxis_hit, table.clone(death_note))
end]]
local is_melee = death_note.damstate and death_note.damstate.is_melee
local is_archery = death_note.damstate and death_note.damstate.is_archery
if damtype ~= 'PHYSICAL' and not is_melee then return end
local reduction = math.min(t.getDamReduce(self, t), cb.value)
cb.value = cb.value - reduction
game:delayedLogDamage(src, self, 0, ("#SLATE#(%d to logia)#LAST#"):format(reduction), false)
doLogiaReflect(self, reduction, src)
return cb
end,
sustain_slots = 'celestial_logia',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = {}
ret.particle = self:addParticles(Particles.new("vschol_logia", 1))
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
return true
end,
short_desc = function(self, t)
return ("Reduces all physical or weapon damage by %d."):format(t.getDamReduce(self, t))
end,
info = function(self, t)
return ([[Profess the mysteries of the Void, causing malicious blades to recede from sight. Reduces all physical or weapon damage by %d.
Damage reduction improves with Spellpower.]]):
format(t.getDamReduce(self, t))
end,
}
local function get_src_talent (src)
local source_talent = src.__projecting_for and src.__projecting_for.project_type and (src.__projecting_for.project_type.talent_id or src.__projecting_for.project_type.talent) and src.getTalentFromId and src:getTalentFromId(src.__projecting_for.project_type.talent or src.__projecting_for.project_type.talent_id)
if not source_talent then
--if we can't find a source talent try to pull one directly from the actor's talent use record
--this is probably prone to false positives but it catches things like Phantasmal Shield or Black Ice that don't flag their damage correctly :)
local source_tid = src._temp_data and src._temp_data.current_talent and src._temp_data.current_talent[#src._temp_data.current_talent] or nil
source_talent = source_tid and src:getTalentFromId(source_tid) or nil
end
return source_talent
end
newTalent{
name = "Logia of Dysnomy",
short_name = "VSCHOL_LOGIA_OF_DYSNOMY",
type = {"celestial/vs-logia-logia",1},
mode = "sustained",
hide = true,
require = divi_req1,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
no_energy = true,
dont_provide_pool = true,
tactical = tactics_dysnomy,
getDamReduce = function(self, t)
return self:combatTalentSpellDamage(t, 5, 80)
end,
callbackOnHit = function(self, t, cb, src, death_note)
if not src or not src.x then return end
--for debugging purposes; saves a copy of the src reference in the player for analysis
--[[do
getfenv(0).vschol_debug = getfenv(0).vschol_debug or {}
getfenv(0).vschol_debug.dysnomy_hit = getfenv(0).vschol_debug.dysnomy_hit or {}
table.insert(getfenv(0).vschol_debug.dysnomy_hit, table.clone(src))
end]]
local is_melee = death_note and death_note.damstate and death_note.damstate.is_melee
local is_archery = death_note and death_note.damstate and death_note.damstate.is_archery
if is_melee or is_archery then return end
if core.fov.distance(self.x, self.y, src.x, src.y) <= 1 then return end
local effect_id = src.__project_source and src.__project_source.effect_id
local source_talent = get_src_talent(src)
local area_effect = src.__project_source and src.__project_source.vschol_map_effect
area_effect = area_effect or (src.__project_source and src.__project_source.__CLASSNAME and (src.__project_source.__CLASSNAME == 'mod.class.Object'))
--if we detect that there is an intermediary source that is either a map effect or an inanimate object let's assume this falls under "area effect" and call it a day
--false positives are probably better than false negatives
--game.log(tostring(source_talent))
--game.log(tostring(effect_id))
if not (source_talent or effect_id or area_effect) then return end
if not ((source_talent and (source_talent.is_spell or source_talent.is_mind or source_talent.is_nature)) or effect_id or area_effect) then return end
local reduction = math.min(t.getDamReduce(self, t), cb.value)
cb.value = cb.value - reduction
game:delayedLogDamage(src, self, 0, ("#SLATE#(%d to logia)#LAST#"):format(reduction), false)
doLogiaReflect(self, reduction, src)
return cb
end,
sustain_slots = 'celestial_logia',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = {}
ret.particle = self:addParticles(Particles.new("vschol_logia", 1))
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
return true
end,
short_desc = function(self, t)
return ("Reduces all spell, mindpower, wildgift, area effect, and status effect damage from nonadjacent sources by %d."):format(t.getDamReduce(self, t))
end,
info = function(self, t)
return ([[Profess the mysteries of the Void, causing hostile observers to recede from sight. Reduces all spell, mindpower, wildgift, area effect, and status effect damage from nonadjacent sources by %d.
Damage reduction improves with Spellpower.]]):
format(t.getDamReduce(self, t))
end,
}
newTalent{
name = "Logia of Antiposition",
short_name = "VSCHOL_LOGIA_OF_ANTIPOSITION",
type = {"celestial/vs-logia-logia",1},
mode = "sustained",
hide = true,
require = divi_req1,
points = 5,
cooldown = 12,
sustain_vschol_void = 20,
no_energy = true,
dont_provide_pool = true,
tactical = tactics_antiposition,
range = 6,
getDamReduce = function(self, t)
return self:combatTalentSpellDamage(t, 5, 100)
end,
callbackOnHit = function(self, t, cb, src, death_note)
if not src or not src.x then return end
if core.fov.distance(self.x, self.y, src.x, src.y) < t.range then return end
local reduction = math.min(t.getDamReduce(self, t), cb.value)
cb.value = cb.value - reduction
game:delayedLogDamage(src, self, 0, ("#SLATE#(%d to logia)#LAST#"):format(reduction), false)
doLogiaReflect(self, reduction, src)
return cb
end,
sustain_slots = 'celestial_logia',
activate = function(self, t)
game:playSoundNear(self, "talents/spell_generic2")
local ret = {}
ret.particle = self:addParticles(Particles.new("vschol_logia", 1))
return ret
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
return true
end,
short_desc = function(self, t)
return ("Reduces all damage from sources at least %d tiles away by %d."):format(t.range, t.getDamReduce(self, t))
end,
info = function(self, t)
return ([[Profess the mysteries of the Void, causing distant foes to recede from sight. Reduces all damage from sources at least %d tiles away by %d.
Damage reduction improves with Spellpower.]]):
format(t.range, t.getDamReduce(self, t))
end,
}
newTalent{
name = "Logia Acolyte",
short_name = "VSCHOL_LOGIA_ACOLYTE",
type = {"celestial/vs-logia", 1},
require = divi_req1,
points = 5,
mode = "passive",
vschol_void = 0, -- forces learning of Void pool
passives = function(self, t)
self:setTalentTypeMastery("celestial/vs-logia-logia", self:getTalentMastery(t))
end,
on_learn = function(self, t)
self:learnTalent(self.T_VSCHOL_LOGIA_OF_APRAXIS, true, nil, {no_unlearn=true})
self:learnTalent(self.T_VSCHOL_LOGIA_OF_DYSNOMY, true, nil, {no_unlearn=true})
self:learnTalent(self.T_VSCHOL_LOGIA_OF_ANTIPOSITION, true, nil, {no_unlearn=true})
end,
on_unlearn = function(self, t)
self:unlearnTalent(self.T_VSCHOL_LOGIA_OF_APRAXIS)
self:unlearnTalent(self.T_VSCHOL_LOGIA_OF_DYSNOMY)
self:unlearnTalent(self.T_VSCHOL_LOGIA_OF_ANTIPOSITION)
end,
info = function(self, t)
local ret = ""
local old1 = self.talents[self.T_VSCHOL_LOGIA_OF_APRAXIS]
local old2 = self.talents[self.T_VSCHOL_LOGIA_OF_DYSNOMY]
local old3 = self.talents[self.T_VSCHOL_LOGIA_OF_ANTIPOSITION]
self.talents[self.T_VSCHOL_LOGIA_OF_APRAXIS] = (self.talents[t.id] or 0)
self.talents[self.T_VSCHOL_LOGIA_OF_DYSNOMY] = (self.talents[t.id] or 0)
self.talents[self.T_VSCHOL_LOGIA_OF_ANTIPOSITION] = (self.talents[t.id] or 0)
local good, errorMsg = pcall(function() -- Be very paranoid, even if some addon or whatever manage to make that crash, we still restore values
local t1 = self:getTalentFromId(self.T_VSCHOL_LOGIA_OF_APRAXIS)
local t2 = self:getTalentFromId(self.T_VSCHOL_LOGIA_OF_DYSNOMY)
local t3 = self:getTalentFromId(self.T_VSCHOL_LOGIA_OF_ANTIPOSITION)
ret = ([[Profess the mysteries of the Void, causing threats to recede from sight. Learning this talent grants you three damage-reducing Logia:
Logia of Apraxis: %s
Logia of Dysnomy: %s
Logia of Antiposition: %s
You may only have one Logia active at a time.
Damage reduction improves with Spellpower.]]):
format(t1.short_desc(self, t1), t2.short_desc(self, t2), t3.short_desc(self, t3))
end)
if not good then ret = errorMsg end
self.talents[self.T_VSCHOL_LOGIA_OF_APRAXIS] = old1
self.talents[self.T_VSCHOL_LOGIA_OF_DYSNOMY] = old2
self.talents[self.T_VSCHOL_LOGIA_OF_ANTIPOSITION] = old3
return ret
end,
}
newTalent{
name = "Logia Cipher",
short_name = "VSCHOL_LOGIA_CIPHER",
type = {"celestial/vs-logia", 2},
require = divi_req2,
points = 5,
mode = "passive",
getPowerReduction = function(self, t)
--linear scaling bc it's a rescaled stat
return self:getMag() * self:combatTalentScale(t, .25, .50)
end,
getSpeedReduction = function(self, t)
return self:combatTalentLimit(t, 1, .15, .3)
end,
callbackOnDealDamage = function(self, t, val, tgt, dead, death_note)
if not death_note then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
if damtype ~= 'ARCANE' then return end
if self:isTalentActive(self.T_VSCHOL_LOGIA_OF_APRAXIS) then
tgt:setEffect(tgt.EFF_VSCHOL_CIPHER_APRAXIS, 3, {apply_power = self:combatSpellpower()-10, power = t.getPowerReduction(self, t)})
elseif self:isTalentActive(self.T_VSCHOL_LOGIA_OF_DYSNOMY) then
tgt:setEffect(tgt.EFF_VSCHOL_CIPHER_DYSNOMY, 3, {apply_power = self:combatSpellpower()-10, power = t.getPowerReduction(self, t)})
elseif self:isTalentActive(self.T_VSCHOL_LOGIA_OF_ANTIPOSITION) then
tgt:setEffect(tgt.EFF_VSCHOL_CIPHER_ANTIPOSITION, 3, {apply_power = self:combatSpellpower()-10, power = t.getSpeedReduction(self, t)})
end
end,
info = function(self, t)
return ([[The reality-warping power of your Logia now infuses your spells, causing any Arcane damage you deal to distort space around your targets. This causes the following debilitating effects based on which Logia you have sustained:
Logia of Apraxis: Reduces Accuracy by %d.
Logia of Dysnomy: Reduces Mindpower and Spellpower by %d.
Logia of Antiposition: Reduces movement speed by %d%%.
These effects can be saved against (Spellpower - 10 vs. Spell Save).
The Accuracy and Spell/Mindpower penalties scale with your Magic stat.]])
:format(t.getPowerReduction(self, t), t.getPowerReduction(self, t), t.getSpeedReduction(self, t) * 100)
end,
}
newTalent{
name = "Logia Savant",
short_name = "VSCHOL_LOGIA_SAVANT",
type = {"celestial/vs-logia", 3},
require = divi_req3,
points = 5,
mode = "passive",
--potentially annoying on npcs so it might be disabled
--no_npc_use = true,
getDisadvantage = function(self, t)
return self:combatTalentLimit(t, 0, 20, 10)
end,
callbackOnTakeDamage = function(self, t, src, x, y, type, dam, state)
if not (src and src.x and src.setEffect) then return end
--only triggers once per target per turn
if self.turn_procs.vschol_savant and self.turn_procs.vschol_savant[src.uid] then return nil end
self.turn_procs.vschol_savant = self.turn_procs.vschol_savant or {}
self.turn_procs.vschol_savant[src.uid] = true
if not self:checkHit(self:combatSpellpower() - t.getDisadvantage(self, t), src:combatSpellResist(), 0, 100, 0) then
game.logSeen(src, "%s resists the logia's backlash!", src.name)
return end
if self:isTalentActive(self.T_VSCHOL_LOGIA_OF_APRAXIS) then
local dist = core.fov.distance (self.x, self.y, src.x, src.y)
if dist <= 1 and src:canBe("pin") then
src:setEffect(src.EFF_PINNED, 2, {})
end
elseif self:isTalentActive(self.T_VSCHOL_LOGIA_OF_DYSNOMY) then
local source_talent = get_src_talent(src)
if source_talent and (source_talent.is_spell or source_talent.is_mind) and source_talent.mode == 'activated' then
game:onTickEnd(function()
local coolDown = src.talents_cd[source_talent.id] or 0
local extend = math.ceil(coolDown * .2)
if extend > 0 then
src:alterTalentCoolingdown(source_talent.id, extend)
game.log(("#YELLOW#%s's %s's cooldown was increased by %d %s!#LAST#"):tformat(src.name, source_talent.name, extend, extend > 1 and _t'turns' or _t'turn'))
end
end)
end
elseif self:isTalentActive(self.T_VSCHOL_LOGIA_OF_ANTIPOSITION) then
local dist = core.fov.distance (self.x, self.y, src.x, src.y)
if dist >= 4 and dist <= 9 and src:canBe("knockback") then
game:onTickEnd(function()
src:knockback(self.x, self.y, 1)
end)
end
end
end,
info = function(self, t)
return ([[Your deep knowledge of the Void grants your Logia even greater power, creating a bubble of unstable space around yourself that reacts violently to damage. Any targets that damage you must make a spell save against your Spellpower - %d or suffer various forms of backlash based on your currently sustained Logia:
Logia of Apraxis: Pins adjacent targets for 2 turns.
Logia of Dysnomy: If directly damaged by a spell or mindpower, increases its cooldown by 20%% (rounding up).
Logia of Antiposition: If the source is between 4 and 9 tiles away (inclusive), knocks them back one tile.
The penalty to your Spellpower reduces with talent level.]])
:format(t.getDisadvantage(self, t))
end,
}
newTalent{
name = "Logia Enigma",
short_name = "VSCHOL_LOGIA_ENIGMA",
type = {"celestial/vs-logia", 4},
require = divi_req4,
points = 5,
mode = "passive",
getMultiplier = function(self, t)
return self:combatTalentScale(t, 1, 1.5)
end,
--killing ourselves on our own damage is annoying, having it happen because of a passive which may or may not be there
--and requires us to check the talent sheet to see is fuggin intolerable
no_npc_use = true,
info = function(self, t)
return ([[Your mastery of the Void's unknowable truths has crystallised into a pure clarity, granting your Logia such depth that they bend reality back upon itself. %d%% of all damage blocked by your Logia is reflected back upon the attacker as Arcane damage.
Damage multiplier improves with talent level.]])
:format(t.getMultiplier(self, t) * 100)
end,
}PK oO��S �S data/talents/subspace.lua
local Object = require "mod.class.Object"
newTalent{
name = "Voidwalk",
short_name = "VSCHOL_VOIDWALK",
type = {"celestial/vs-subspace",1},
mode = "sustained",
require = divi_req1,
points = 5,
cooldown = 8,
vschol_void = 5,
no_energy = true,
tactical = {ESCAPE = 3},
getMove = function(self, t)
return math.floor(self:combatTalentScale(t, 3, 5.8))
end,
iconOverlay = function(self, t, p)
local p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
local val = p.moves or 0
local fnt = "buff_font_small"
--if val >= 1000 then fnt = "buff_font_smaller" end
return ("%d"):format(val), fnt
end,
--[[callbackOnAct = function(self, t)
game.log("OnAct")
game.log("energy: "..tostring(self.energy.value))
--save our energy at the beginning of each turn for later reference
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
p._energy = self.energy.value
end,
callbackOnMove = function(self, t, moved, forced, ox, oy, x, y)
game.log("OnMove")
game.log("energy: "..tostring(self.energy.value))
if not forced and self.x ~= ox or self.y ~= oy then
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
--note if we moved successfully for later
p._moved = true
p._energy = self.energy.value
game.log("om: "..tostring(p._moved))
game.level.map:particleEmitter(self.x, self.y, 1, "teleport")
end
end,
callbackOnActEnd = function(self, t)
game.log("OnActEnd")
game.log("energy: "..tostring(self.energy.value))
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
game.log("oae: "..tostring(p._moved))
if p._moved then
game.log("oae: "..tostring(self.energy.value))
local energy = p._energy or 0
--refund our energy to what it was before we moved
local used = energy - self.energy.value
self.energy.value = energy
--take energy out of the sustain
p.moves = p.moves - (used / game.energy_to_act)
--cleanup
p._energy = nil
p._moved = nil
if p.moves <= 0 then
self:forceUseTalent(self.T_VSCHOL_VOIDWALK, {ignore_energy=true})
end
end
end,]]
--hack, stops other creatures from getting turn energy or taking turns entirely between our moves
--saves energy for all creatures on the map
--[[saveEnergy = function(self, t)
game.level.map:applyAll(function (x, y, key, entity)
if entity ~= self and entity.energy and entity.act then
--game.log (entity.name or "unnamed")
entity._vschol_voidwalk_savedenergy = entity.energy.value
--game.log ((entity.name or "unnamed") .. ": saved energy: "..tostring(entity._vschol_voidwalk_savedenergy))
end
end)
end,
--zeros energy, notes that we did
zeroEnergy = function(self, t)
game.level.map:applyAll(function (x, y, key, entity)
if entity ~= self and entity.energy and entity._vschol_voidwalk_savedenergy then
entity.energy.value = 0
--game.log ((entity.name or "unnamed") .. ": negated energy")
entity._vschol_voidwalk_zeroenergy = true
end
end)
end,
--reapplies energy to zero'd creatures
restoreEnergy = function(self, t)
game.level.map:applyAll(function (x, y, key, entity)
if entity ~= self and entity.energy and entity._vschol_voidwalk_savedenergy then
if entity._vschol_voidwalk_zeroenergy then
--game.log ((entity.name or "unnamed") .. ": restored energy: "..tostring(entity._vschol_voidwalk_savedenergy))
entity.energy.value = entity._vschol_voidwalk_savedenergy
end
entity._vschol_voidwalk_zeroenergy = nil
entity._vschol_voidwalk_savedenergy = nil
end
end)
end,
callbackOnAct = function(self, t)
t.restoreEnergy(self, t)
--save our energy at the beginning of each turn for later reference
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
if not p then return end
p.energy = self.energy.value
t.saveEnergy(self, t)
end,
callbackOnMove = function(self, t, moved, forced, ox, oy, x, y)
if not forced and self.x ~= ox or self.y ~= oy then
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
if not p then return end
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_voidwalk")
local energy = p.energy or 0
--refund our energy to what it was at the start of the turn
local used = energy - self.energy.value
self.energy.value = energy
--take energy out of the sustain
p.moves = p.moves - 1
if p.moves <= 0 then
self:forceUseTalent(self.T_VSCHOL_VOIDWALK, {ignore_energy=true})
else
t.zeroEnergy(self, t)
end
end
end,
--I made a mistake initially designing this; I thought the order was:
--use ability
--onmove
--use energy
--actend
--turn end, regain energy
--onact
--onmove
--use energy
--actend
--turn end, regain energy
--onact
--but actually it was:
--use ability, take any action
--use energy
--actend
--onmove
--turn end, regain energy
--onact
--use energy
--actend
--onmove
--turn end, regain energy
--onact
activate = function(self, t)
local ret = {
moves = t.getMove(self, t),
energy = self.energy.value
}
t.saveEnergy(self, t)
return ret
end,
deactivate = function(self, t, p)
t.restoreEnergy(self, t)
return true
end,]]
callbackOnMove = function(self, t, moved, forced, ox, oy, x, y)
if not forced and self.x ~= ox or self.y ~= oy then
p = self:isTalentActive(self.T_VSCHOL_VOIDWALK)
if not p then return end
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_voidwalk")
--take energy out of the sustain
p.moves = p.moves - 1
if p.moves <= 0 then
self:forceUseTalent(self.T_VSCHOL_VOIDWALK, {ignore_energy=true})
end
end
end,
--1.7 has native no-energy movement functionality so i can just yeet the above thankfully
activate = function(self, t)
local ret = {
moves = t.getMove(self, t),
}
self:talentTemporaryValue(ret, "free_movement", 1)
return ret
end,
deactivate = function(self, t, p)
return true
end,
--praise darkgod
--im leaving the old code as a monument to my stupidity
info = function(self, t)
return ([[Dive below normal reality into the Void's undercurrents, in which you may move without using a turn up to %d times before being forced back into conventional spacetime. Actions other than moving will still take time, but will not interrupt the effect.
The number of free movements you can take improves with talent level.]])
:format(t.getMove(self, t))
end,
}
local ap_collapsechance = 12.5
--displaces an actor to the nearest walkable tile in the specified radius
--global function to avoid spoopy upvalues :-)
function vschol_displaceActor (actor, r)
local tiles = {}
--game.log("starting func")
local shortest_dist = r
for i = actor.x-r, actor.x+r do
for j = actor.y-r, actor.y+r do
local dist = core.fov.distance(actor.x, actor.y, i, j)
if game.level.map:isBound(i, j) and dist <= shortest_dist and actor:canMove(i, j) then
-- Check for no_teleport and vaults
if game.level.map.attrs(i, j, "no_teleport") then
local vault = game.level.map.attrs(actor.x, actor.y, "vault_id")
if vault and game.level.map.attrs(i, j, "vault_id") == vault then
tiles[#tiles+1] = {x=i,y=j,dist=dist}
shortest_dist = math.min(shortest_dist, dist)
end
else
tiles[#tiles+1] = {x=i,y=j,dist=dist}
shortest_dist = math.min(shortest_dist, dist)
end
end
end
end
--game.log("tiles found :"..tostring(#tiles))
local close_tiles = {}
for i = 1, #tiles do
if tiles[i].dist <= shortest_dist then
close_tiles[#close_tiles+1] = tiles[i]
end
end
tiles = close_tiles
--game.log("close tiles found :"..tostring(#tiles))
if #tiles > 0 then
local tile = tiles[rng.range(1, #tiles)]
actor:move(tile.x, tile.y, true)
return true
end
end
newTalent{
name = "Antiphase",
short_name = "VSCHOL_ANTIPHASE",
type = {"celestial/vs-subspace", 2},
mode = "sustained",
require = divi_req2,
points = 5,
cooldown = 24,
vschol_void = 5,
--no_energy = true,
tactical = {ESCAPE = 3},
--npcs will never ever ever be able to use this intelligently
no_npc_use = true,
getDrain = function(self, t)
return self:combatTalentLimit(t, 0, 5, 2.5)
end,
makeHole = function(self, t, x, y)
if not game.level.map:isBound(x, y) then return end
local oe = game.level.map(x, y, Map.TERRAIN)
if not oe or oe.special then return end
if not oe or oe:attr("temporary") or not oe.dig or not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") then return end
local e = Object.new{
old_feat = oe,
name = oe.name, image = oe.image,
desc = oe.desc,
type = oe.type,
display = oe.display, color=colors.WHITE, back_color=colors.BLACK,
show_tooltip = true,
block_sight = false,
temporary = true,
x = x, y = y,
canAct = false,
collapsechance = ap_collapsechance,
add_displays = oe.add_displays,
add_mos = oe.add_mos,
displace = vschol_displaceActor,
act = function(self)
local Map = require "engine.Map"
self:useEnergy()
if not self.summoner:isTalentActive(self.summoner.T_VSCHOL_ANTIPHASE) then
if rng.percent(self.collapsechance) then
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
game.level.map(self.x, self.y, engine.Map.TERRAIN, self.old_feat)
game.level:removeEntity(self)
--move actors on the collapsing tile away
local actor = game.level.map(self.x, self.y, Map.ACTOR)
if actor then
if not actor.turn_procs.vschol_displaced then
actor.turn_procs.vschol_displaced = true
game.logSeen(actor, "%s is displaced out of the collapsing wall!", actor.name)
end
self.displace(actor, 100)
end
end
end
end,
summoner_gain_exp = true,
summoner = self,
}
e.tooltip = mod.class.Grid.tooltip
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN, e)
--game.level.map:updateMap(x, y)
e.particles = Particles.new("vschol_antiphase", 1, {})
e.particles.x = x
e.particles.y = y
game.level.map:addParticleEmitter(e.particles)
end,
carveHoles = function(self, t)
for i = -1, 1 do
for j = -1, 1 do
if i ~= 0 or j ~= 0 then
t.makeHole(self, t, self.x + i, self.y + j)
end
end
end
end,
callbackOnActBase = function(self, t)
self:incVschol_void(-t.getDrain(self, t))
if self.vschol_void <= 0 then
self:forceUseTalent(self.T_VSCHOL_ANTIPHASE, {ignore_energy=true})
end
end,
callbackOnMove = function(self, t, moved, forced, ox, oy, x, y)
if not forced and self.x ~= ox or self.y ~= oy then
t.carveHoles(self, t)
self:incVschol_void(-t.getDrain(self, t))
if self.vschol_void <= 0 then
self:forceUseTalent(self.T_VSCHOL_ANTIPHASE, {ignore_energy=true})
end
end
end,
activate = function(self, t)
local ret = {}
t.carveHoles(self, t)
game:playSoundNear(self, "talents/dispel")
return ret
end,
deactivate = function(self, t, p)
return true
end,
info = function(self, t)
return ([[Saturate your surroundings with Void energy, shifting adjacent walls out of phase as you move near them and rendering them temporarily passable. All disrupted walls will remain in this state so long as this talent is sustained, after which they have a %0.1f%% chance of returning to normal each game turn, displacing any creatures within to the nearest empty tile. Maintaining this effect is exhausting, draining %0.1f Void Energy per turn and %0.1f additional Void Energy each time you move while sustained.
Energy efficiency improves with talent level.]])
:format(ap_collapsechance, t.getDrain(self, t), t.getDrain(self, t))
end,
}
newTalent{
name = "Subduction",
short_name = "VSCHOL_SUBDUCTION",
type = {"celestial/vs-subspace",3},
mode = "sustained",
require = divi_req3,
points = 5,
cooldown = 24,
vschol_void = 20,
range = 6,
radius = 2,
--no_energy = true,
tactical = { DISABLE = 3 },
--[[getDrain = function(self, t)
return self:combatTalentLimit(t, 0, 8, 4)
end,]]
--npcs seem to ignore that they can project through the hole and just go around :T
no_npc_use = true,
getMaxDur = function(self, t)
return math.round(self:combatTalentScale(t, 4, 6.8))
end,
iconOverlay = function(self, t, p)
local p = self:isTalentActive(t.id)
local val = p.duration or 0
local fnt = "buff_font_small"
--if val >= 1000 then fnt = "buff_font_smaller" end
return ("%d"):format(val), fnt
end,
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
makeHole = function(self, t, x, y)
if not game.level.map:isBound(x, y) then return end
local oe = game.level.map(x, y, Map.TERRAIN)
if not oe or oe.special then return end
if oe:attr("temporary") then return end
if game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not oe.dig then return end
local e = Object.new{
old_feat = oe,
name = oe.name, image = oe.image,
desc = oe.desc,
type = oe.type,
display = oe.display, color=colors.WHITE, back_color=colors.BLACK,
show_tooltip = true,
block_sight = false,
always_remember = true,
does_block_move = true,
block_move = true,
pass_projectile = true,
temporary = true,
x = x, y = y,
canAct = false,
vs_subduction = true,
add_displays = oe.add_displays,
add_mos = oe.add_mos,
displace = vschol_displaceActor,
act = function(self)
local Map = require "engine.Map"
self:useEnergy()
if not self.summoner:isTalentActive(self.summoner.T_VSCHOL_SUBDUCTION) then
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
game.level.map(self.x, self.y, engine.Map.TERRAIN, self.old_feat)
game.level:removeEntity(self)
--[[--move actors on the collapsing tile away
local actor = game.level.map(self.x, self.y, Map.ACTOR)
if actor and self.old_feat.block_move then
if not actor.turn_procs.vschol_displaced then
actor.turn_procs.vschol_displaced = true
game.logSeen(actor, "%s is displaced out of the collapsing wall!", actor.name)
end
self.displace(actor, 100)
end]]
end
end,
summoner_gain_exp = true,
summoner = self,
}
e.tooltip = mod.class.Grid.tooltip
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN, e)
--game.level.map:updateMap(x, y)
e.particles = Particles.new("vschol_subduction", 1, {})
e.particles.x = x
e.particles.y = y
e.particles.zdepth = 6
game.level.map:addParticleEmitter(e.particles)
end,
--[[callbackOnActBase = function(self, t)
self:incVschol_void(-t.getDrain(self, t))
if self.vschol_void <= 0 then
self:forceUseTalent(self.T_VSCHOL_SUBDUCTION, {ignore_energy=true})
end
end,]]
callbackOnActBase = function(self, t)
local p = self:isTalentActive(t.id)
p.duration = (p.duration or 0) - 1
if p.duration <= 0 then
self:forceUseTalent(self.T_VSCHOL_SUBDUCTION, {ignore_energy=true})
end
end,
on_pre_use_ai = function (self, talent, silent, fake)
local target = self.ai_target and self.ai_target.actor
-- if they're adjacent then this talent isnt useful for creating distance
if self.x and target and core.fov.distance(self.x, self.y, target.x, target.y) <= 1 then
return false
end
return true
end,
activate = function(self, t)
local tg = self:getTalentTarget(t)
local x, y
if self:playerControlled() then
x, y = self:getTarget(tg)
else
--ais should try to aim the hole between themselves and the player (to push them away and block them)
local aitarget = self.ai_target and self.ai_target.actor
if aitarget and aitarget.x then
local dx, dy = aitarget.x - self.x, aitarget.y - self.y
dx = dx < 0 and math.floor(dx/2) or math.ceil(dx/2)
dy = dy < 0 and math.floor(dy/2) or math.ceil(dy/2)
x, y = self.x + dx, self.y + dy
end
end
if not x or not y then return nil end
local _ _, x, y = self:canProject(tg, x, y)
--keep a list of actors to displace so we can do em all at once after the subduction :)
local actors = {}
self:project(tg, x, y, function(tx, ty)
t.makeHole(self, t, tx, ty)
local actor = game.level.map(tx, ty, Map.ACTOR)
if actor then
actors[#actors+1] = actor
end
end)
if #actors > 0 then
for i=1, #actors do
vschol_displaceActor(actors[i], 10)
end
end
game:playSoundNear(self, "talents/echo")
return {
duration = t.getMaxDur(self, t)
}
end,
--search the entire map on deactivate and close any holes; for responsivity
deactivate = function(self, t, p)
--lambda function wrapper within a lambda function wrapper, approx. 37% cursed
game:onTickEnd(function()
game.level.map:applyAll(function (x, y, key, entity)
if entity:attr("vs_subduction") and entity.act then
entity.act(entity)
end
end)
end)
return true
end,
info = function(self, t)
return ([[Plunge a radius-2 area into the depths of subspace, rendering it impossible to traverse while still allowing projectiles and sight to pass over it for as long as this talent is sustained. You can maintain this effect for up to %d turns; upon deactivation, the affected area immediately returns to normal.
Creatures in the subduction zone upon activation will be displaced into the nearest passable terrain within 10 tiles.
Duration improves with talent level.]])
:format(t.getMaxDur(self, t))
end,
}
local function escapeGrid(self, cx, cy, radius, ax, ay, want_range)
ax, ay = ax or cx, ay or cy
want_range = want_range or radius
local grid = {x = cx, y=cy, dist = math.huge}
local dist
core.fov.calc_circle(cx, cy, game.level.map.w, game.level.map.h, radius,
function(d, lx, ly) -- block
end,
function(d, lx, ly) -- apply
if self:canMove(lx, ly) then
dist = math.abs(core.fov.distance(ax, ay, lx, ly) - want_range) + rng.float(0, .1)
if math.abs(dist) < grid.dist then
grid.dist = dist
grid.x = lx; grid.y = ly
end
end
end,
nil)
if dist then return grid.x, grid.y, grid.dist end
end
--perfectly-accurate targeted teleport, but you need to make space first to use it as an escape
newTalent{
name = "Tesseract",
short_name = "VSCHOL_TESSERACT",
type = {"celestial/vs-subspace", 4},
require = divi_req4,
points = 5,
range = function(self, t)
return math.floor(self:combatTalentScale(t, 8, 16))
end,
vschol_void = 30,
tactical = { ESCAPE = 4 },
requires_target = true,
is_teleport = true,
cooldown = 40,
radius = 4,
carry_chance = 75,
target = function(self, t)
local tg = {type="hit", x = self.x, y = self.y, nolock=true, pass_terrain=true, nowarning=true, range=self:getTalentRange(t), requires_knowledge=false}
return tg
end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local x, y
if self:playerControlled() then
x, y = self:getTarget(tg)
else
local aitarget = self.ai_target and self.ai_target.actor
local tx, ty = self:aiSeeTargetPos(aitarget)
--teleport as far away as possible
x, y = escapeGrid(self, self.x, self.y, 0, tx, ty, self:getTalentRange(t))
end
if not x or not y then return nil end
local _ _, x, y = self:canProject(tg, x, y)
local carried = {}
self:project({type='ball', radius=self:getTalentRadius(t), selffire = false}, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target and rng.percent(t.carry_chance) then
carried[#carried+1] = target
end
end)
local function canTeleport ()
-- Check for no_teleport and vaults
if game.level.map.attrs(x, y, "no_teleport") then
local vault = game.level.map.attrs(self.x, self.y, "vault_id")
if vault and game.level.map.attrs(x, y, "vault_id") == vault then
return true
end
else
return true
end
end
if game.level.map:isBound(x, y) and self:canMove(x, y) and canTeleport() then
self:teleportRandom(x, y, 0)
else
game.logSeen(self, "The teleport is blocked and goes haywire!")
self:teleportRandom(self.x, self.y, self:getTalentRange(t))
end
for i = 1, #carried do
carried[i]:teleportRandom(self.x, self.y, 3)
end
--game:onTickEnd(function()
game:playSoundNear(self, "talents/warp")
--end)
return true
end,
info = function(self, t)
return ([[Briefly draw out reality into a long, infinitely-fine string, allowing you to seamlessly jump to a targeted location in range %d before releasing the firmament back to its natural form. The extreme distortion has a %d%% chance of carrying other targets in radius %d along with you, depositing them randomly within 3 tiles of your destination. Attempting to teleport to an invalid tile will cause you to lose control of your jump and land randomly.
The range improves with talent level.]])
:format(self:getTalentRange(t), t.carry_chance, self:getTalentRadius(t))
end,
}PK ����/ / data/talents/terminus.lua
newTalent{
name = "Termination",
short_name = "VSCHOL_TERMINATION",
type = {"celestial/vs-terminus", 1},
require = divi_req_high1,
points = 5,
mode = "sustained",
sustain_vschol_void = 20,
cooldown = 20,
tactical = { BUFF = 3 },
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 80) end,
base_perc = 30,
inc_perc = 7.5,
callbackOnDealDamage = function(self, t, val, tgt, dead, death_note)
if self.turn_procs._vschol_terminus_proc then return end
if not death_note then return end
if not tgt or not tgt.life then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
if damtype ~= 'TEMPORAL' then return end
local num = 0
-- Count all our non-other detri effects
for eff_id, p in pairs(tgt.tmp) do
local e = tgt.tempeffect_def[eff_id]
if e.type ~= "other" and e.status == "detrimental" then
num = num+1
end
end
local perc = (t.base_perc + t.inc_perc * num)/100
if (tgt.life - tgt.die_at)/(tgt.max_life - tgt.die_at) <= perc then
self.turn_procs.vschol_terminus = self.turn_procs.vschol_terminus or {}
self.turn_procs.vschol_terminus[tgt.uid] = (self.turn_procs.vschol_terminus[tgt.uid] or 0) + 1
local damage = t.getDamage(self, t)
--crits once globally per turn to prevent obscene amounts of spell crit procs
self.turn_procs.vschol_terminus_critmult = self.turn_procs.vschol_terminus_critmult or self:spellCrit(1)
local mult = self.turn_procs.vschol_terminus[tgt.uid]^.5 - (self.turn_procs.vschol_terminus[tgt.uid] - 1)^.5
self.turn_procs._vschol_terminus_proc = true
DamageType:get(DamageType.TEMPORAL).projector(self, tgt.x, tgt.y, DamageType.TEMPORAL, damage * mult)
self.turn_procs._vschol_terminus_proc = nil
if self:knowTalent(self.T_VSCHOL_COSMIC_DEVOURER) and self:reactionToward(tgt) < 0 then
local t_Dev = self:getTalentFromId(self.T_VSCHOL_COSMIC_DEVOURER)
t_Dev.gainEnergy(self, t_Dev)
end
if self:knowTalent(self.T_VSCHOL_HEAT_DEATH) and self:reactionToward(tgt) < 0 then
local t_HD = self:getTalentFromId(self.T_VSCHOL_HEAT_DEATH)
game:onTickEnd(function()
t_HD.doEffect(self, t_HD)
end)
end
end
end,
activate = function(self, t)
game:playSoundNear(self, "talents/spell_echo")
local ret = {}
self:addShaderAura("vschol_termination", "awesomeaura", {time_factor=20000, alpha=.5, flame_scale=1.5}, "particles_images/vschol_termination.png")
return ret
end,
deactivate = function(self, t, p)
self:removeShaderAura("vschol_termination")
return true
end,
info = function(self, t)
return ([[Usher the crumbling dregs of this fragile world into the Void's embrace. While sustained, whenever you deal Temporal damage to a target that leaves them below %d%% of their maximum life, they suffer an additional %0.1f temporal damage. Each negative effect on a target increases the threshold by %0.1f%%. The damage can trigger multiple times per turn, with geometrically diminishing returns on damage.
Damage increases with Spellpower.]])
:format(t.base_perc, self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)), t.inc_perc)
end,
}
newTalent{
name = "Cosmic Devourer",
short_name = "VSCHOL_COSMIC_DEVOURER",
type = {"celestial/vs-terminus", 2},
require = divi_req_high2,
points = 5,
mode = 'passive',
getEnergy = function(self, t)
--somewhat weird scaling but since the entire point of this talent is to get a chunk of VE we have to scale it up higher with TL than other sources
--else there's little point putting more than 1 point in it
return venergy_scale(self, t, self:combatTalentScale(t, 10, 15))
end,
gainEnergy = function(self, t)
self.turn_procs.vschol_devourer = (self.turn_procs.vschol_devourer or 0) + 1
local mult = self.turn_procs.vschol_devourer ^ .5 - (self.turn_procs.vschol_devourer - 1) ^ .5
self:incVschol_void(t.getEnergy(self, t) * mult)
end,
info = function(self, t)
return ([[You are the maw into which the dying cosmos flows. Each time Termination triggers, you absorb %0.2f Void Energy from the target's fading existence. This talent can trigger an unlimited amount of times per turn, with geometrically diminishing returns.
Energy gain increases with Spellpower.]])
:format(t.getEnergy(self, t))
end,
}
newTalent{
name = "Heat Death",
short_name = "VSCHOL_HEAT_DEATH",
type = {"celestial/vs-terminus", 3},
require = divi_req_high3,
points = 5,
mode = 'passive',
target = function(self, t)
return {type="ball", range=0, radius=10, friendlyfire=false}
end,
getMaxPower = function(self, t)
return self:combatTalentLimit(t, 100, 25, 50)
end,
stacks = 4,
doEffect = function(self, t)
if self.turn_procs.vschol_heatdeath then return end
local tg = self:getTalentTarget(t)
self:project(tg, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
target:setEffect(target.EFF_VSCHOL_HEAT_DEATH, 3, {power=t.getMaxPower(self, t) / t.stacks, max_stacks = t.stacks})
end
end)
self.turn_procs.vschol_heatdeath = true
end,
info = function(self, t)
return ([[Breathe the last gasps of the dying universe through your crumbling adversaries, withering away what remains of their strength. The first time you proc Termination in a turn, all enemies in radius 10 are numbed and deal %0.1f%% less damage for 3 turns. The effect stacks up to %d times, to a maximum of %d%% reduced damage.
The damage penalty improves with your talent level.]])
:format(t.getMaxPower(self, t)/t.stacks, t.stacks, t.getMaxPower(self, t))
end,
}
newTalent{
name = "Expunge",
short_name = "VSCHOL_EXPUNGE",
type = {"celestial/vs-terminus", 4},
require = divi_req_high4,
points = 5,
--low cost+cd because it's already a bit of a process to set it up :)
vschol_void = 10,
cooldown = 10,
--go for the kill immediately if we can! :D
tactical = { ATTACKAREA = { TEMPORAL = 10 } },
range = 10,
target = function(self, t)
return {type="ball", range=0, radius=self:getTalentRange(t), selffire=false}
end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 360) end,
on_pre_use_ai = function (self, t, silent, fake)
local target = self.ai_target and self.ai_target.actor
if target and target.hasEffect then
local eff = target:hasEffect(target.EFF_VSCHOL_HEAT_DEATH)
if eff and eff.stacks >= 2 then
return true
end
end
return false
end,
action = function(self, t)
local tg = self:getTalentTarget(t)
local critMult = nil--self:spellCrit(1)
local dam = t.getDamage(self, t)
self:project(tg, self.x, self.y, function(tx, ty)
local target = game.level.map(tx, ty, Map.ACTOR)
if target then
local eff = target:hasEffect(target.EFF_VSCHOL_HEAT_DEATH)
if eff and eff.stacks >= 2 then
game.level.map:particleEmitter(target.x, target.y, 1, "vschol_expunge")
critMult = critMult or self:spellCrit(1)
DamageType:get(DamageType.TEMPORAL).projector(self, target.x, target.y, DamageType.TEMPORAL, dam * critMult)
end
end
end)
--allow empty casting
--if not critMult then return end
game:playSoundNear(self, "talents/echo")
return true
end,
info = function(self, t)
return ([[Banish the fading remnants of targets suffering from at least two stacks of Heat Death in radius 10, dealing %d temporal damage to those affected.
Damage improves with Spellpower.]])
:format(self:damDesc(DamageType.TEMPORAL, t.getDamage(self, t)))
end,
}
PK �'�T T data/talents/vacuum.lua
local function getMaxVoidIncrease (self)
local combined_TL = self:getTalentLevel(self.T_VSCHOL_DISRUPT) + self:getTalentLevel(self.T_VSCHOL_RIFT_SURGE) + self:getTalentLevel(self.T_VSCHOL_NULL_LOCUS) + self:getTalentLevel(self.T_VSCHOL_RECOMBINATION)
local maxVoid = math.round(100 * ((self:getCun() / 100) * (combined_TL / 20))^.5)
return maxVoid
end
local function getMaxVoidBlurb (self)
return ("#8b7fba#Studying the vacuum improves your ability to handle Void Energy, increasing your maximum based on the combined level of your Vacuum spells and your Cunning stat (current bonus: %d#8b7fba#)#LAST#"):format(getMaxVoidIncrease(self))
end
newTalent{
name = "Disrupt",
short_name = "VSCHOL_DISRUPT",
type = {"celestial/vs-vacuum", 1},
require = divi_req1,
points = 5,
range = 10,
tactical = { ATTACK = {ARCANE = 2} },
requires_target = true,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 165) end,
getGain = function(self, t)
return venergy_scale(self, t, 10)
end,
callbackOnStatChange = function(self, t, stat, v)
if stat == self.STAT_CUN then
self:updateTalentPassives(t)
end
end,
passives = function(self, t, p)
self:talentTemporaryValue(p, "max_vschol_void", getMaxVoidIncrease(self))
end,
action = function(self, t)
local tg = {type="hit", range=self:getTalentRange(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 target = game.level.map(x, y, Map.ACTOR)
if target then
local dam = self:spellCrit(t.getDamage(self, t))
self:project({type="hit", talent=t}, x, y, DamageType.ARCANE, dam)
if target:reactionToward(self) < 0 then
self:incVschol_void(t.getGain(self, t))
end
end
game.level.map:particleEmitter(x, y, 1, "vschol_disrupt")
game:playSoundNear(self, "talents/arcane")
return true
end,
info = function(self, t)
return ([[Call a fragment of the cosmic vacuum upon a single target, shredding them for %d arcane damage. This generates %0.1f Void Energy when used on a hostile target.
Damage and energy gain improve with your Spellpower.
This spell has no cooldown.
%s]])
:format(self:damDesc(DamageType.ARCANE, t.getDamage(self, t)), t.getGain(self, t), getMaxVoidBlurb(self))
end,
}
newTalent{
name = "Rift Surge",
short_name = "VSCHOL_RIFT_SURGE",
type = {"celestial/vs-vacuum", 2},
require = divi_req2,
points = 5,
range = 6,
radius = 4,
--The spell has a void cost upfront to cast but gives you a net gain overall
vschol_void = 30,
--it can't be used at the start of an encounter but once you've built some capital it lets you gain even more Void quite easily, keeping momentum going
cooldown = 8,
tactical = { ATTACK = {ARCANE = 2} },
--npcs have no concept of delayed returns so they will mostly just immediately blow any resources they have on Filament, etc.
--as such this talent is dubiously purposeful on npcs
no_npc_use = true,
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 140) end,
getGain = function(self, t)
--somewhat weird scaling but since the entire point of this talent is to get a chunk of VE we have to scale it up higher with TL than other sources
--else there's little point putting more than 1 point in it
return t.vschol_void + venergy_scale(self, t, self:combatTalentScale(t, 16, 24))
end,
on_learn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
on_unlearn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
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 dam = self:spellCrit(t.getDamage(self, t))
self:project(tg, x, y, DamageType.ARCANE, dam)
game:onTickEnd(function()
self:incVschol_void(t.getGain(self, t))
end)
game.level.map:particleEmitter(x, y, 1, "vschol_riftsurge", {radius = self:getTalentRadius(t)})
game:playSoundNear(self, "talents/warp")
return true
end,
info = function(self, t)
return ([[Tear open a gate into the heart of the celestial abyss, flooding a radius-4 area with harmful energy that deals %d arcane damage to all targets within. You recapture some of the energy released for re-use, gaining %0.1f Void Energy.
Damage and energy gain improve with your Spellpower.
%s]])
:format(self:damDesc(DamageType.ARCANE, t.getDamage(self, t)), t.getGain(self, t), getMaxVoidBlurb(self))
end,
}
newTalent{
name = "Recombination",
short_name = "VSCHOL_RECOMBINATION",
type = {"celestial/vs-vacuum", 3},
require = divi_req3,
points = 5,
range = 10,
cooldown = 12,
no_energy = true,
vschol_void = function(self, t)
--requires at least 10 void energy
return math.max(10, self.vschol_void * t.getDrain(self, t))
end,
tactical = { BUFF = 3 },
--getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 100) end,
getDrain = function(self, t)
return .75
end,
--npcs have no concept of delayed returns so they will mostly just immediately blow any resources they have on Filament, etc.
--as such this talent is dubiously purposeful on npcs
no_npc_use = true,
getEfficiency = function(self, t)
return 1 + self:combatTalentScale(t, .4, .8)
end,
duration = 4,
on_learn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
on_unlearn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
action = function(self, t)
local regen = (t.vschol_void(self, t) * t.getEfficiency(self, t))/t.duration
self:setEffect(self.EFF_VSCHOL_RECOMBINATION, t.duration, {power = regen})
game:playSoundNear(self, "talents/spell_generic")
return true
end,
info = function(self, t)
return ([[Offer a portion of your gathered Void Energy unto the firmament, triggering a massive backsurge of energy that returns %d%% of the spent Void Energy (not including fatigue) over 5 turns. This spell costs the greater of 10 Void Energy or %d%% of your current pool.
The rate of return improves with talent level.
%s]])
:format(t.getEfficiency(self, t)*100, t.getDrain(self, t) * 100, getMaxVoidBlurb(self))
end,
}
--most of the meat of this talent is handled in the main Void Energy resource code so as to sync it with when Void actually wants to regen
newTalent{
name = "Null Locus",
short_name = "VSCHOL_NULL_LOCUS",
type = {"celestial/vs-vacuum", 4},
require = divi_req4,
points = 5,
mode = "sustained",
no_energy = true,
cooldown = 15,
tactical = { BUFF = 3 },
--unfair on npcs because they have huge hp so dont care about the damage as much
--meanwhile they get to blast you with filament every single turn
no_npc_use = true,
getDamage = function(self, t)
return 40 * (self:combatSpellpower() / 100)--40 * ((100 + self:combatSpellpower()) / 200) ^ .5
end,
getRegen = function(self, t)
return venergy_scale(self, t, 8)
end,
on_learn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
on_unlearn = function(self, t)
self:updateTalentPassives(self:getTalentFromId(self.T_VSCHOL_DISRUPT))
end,
activate = function(self, t)
return {}
end,
deactivate = function(self, t, p)
self:removeParticles(p.particle)
return true
end,
info = function(self, t)
return ([[Invite the Void's emptiness into your own body, gaining %0.2f Void Energy per turn while losing %0.1f life per turn from its overwhelming power. These effects are halted when there are no hostiles in sight, as well as if the damage would kill you.
Self damage and Void Energy gain increase with your Spellpower. The efficiency improves with talent level.
%s]])
:format(t.getRegen(self, t), t.getDamage(self, t), getMaxVoidBlurb(self))
end,
}PK قL'$ '$ data/talents/void.lualocal Object = require "engine.Object"
local Map = require "engine.Map"
newTalent{
name = "Siphon",
short_name = "VSCHOL_SIPHON",
type = {"celestial/vs-void", 1},
require = divi_req1,
points = 5,
range = 10,
cooldown = 6,
tactical = { DISABLE = 5, VSCHOL_VOID = 5 },
requires_target = true,
--getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 100) end,
getReduction = function (self, t)
local baseRed = 20
local scalingRed = 16
--very weird scaling but whatever, combatSpellDamage isn't quite flexible enough for what I want
return (baseRed + scalingRed * (self:combatSpellpower() / 100) ^ .5) * self:combatTalentScale(t, .5, 1)
end,
getGain = function (self, t)
return venergy_scale(self, t, 24)--math.round(5 + self:combatTalentSpellDamage(t, 5, 10))
end,
action = function(self, t)
local tg = {type="hit", range=self:getTalentRange(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 target = game.level.map(x, y, Map.ACTOR)
if target then
local success = false
if self:checkHit(self:combatSpellpower()+10, target:combatSpellResist(), 5, 100, 5) then
success = true
target:setEffect(target.EFF_VSCHOL_SIPHON, 8, {power = t.getReduction(self, t)})
end
if target:reactionToward(self) < 0 then
local mult = .5
if success == true then
mult = 1
end
self:incVschol_void(t.getGain(self, t) * mult)
end
end
game.level.map:particleEmitter(x, y, 1, "vschol_siphon")
game:playSoundNear(self, "talents/warp")
return true
end,
info = function(self, t)
return ([[Call the hungering maw of the Void down upon a target, reducing their damage and healing by %d%% for 8 turns. You then absorb the residual energy from hostile targets, gaining %0.1f Void Energy. The target can save against your Spellpower + 10 to block the debuff and reduce the Void Energy gained by half.
The strength of the debuff improves with your Spellpower.]])
:format(t.getReduction(self, t), t.getGain(self, t))
end,
}
newTalent{
name = "Nullify",
short_name = "VSCHOL_NULLIFY",
type = {"celestial/vs-void", 2},
require = divi_req2,
points = 5,
range = 10,
cooldown = 15,
vschol_void = 10,
tactical = { DEFEND = 3 },
--getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 100) end,
getThreshold = function (self, t)
return self:combatTalentSpellDamage(t, 5, 120)
end,
getAbsorption = function (self, t)
return .05
end,
getDuration = function(self, t)
return math.round(self:combatTalentLimit(t, 15, 4, 6))
end,
action = function(self, t)
self:setEffect(self.EFF_VSCHOL_NULLIFY, t.getDuration(self, t), {power = t.getThreshold(self, t), absorb = t.getAbsorption(self, t)})
game:playSoundNear(self, "talents/spell_generic")
return true
end,
info = function(self, t)
return ([[Enclose yourself in a shell of pure Void energy that reduces all incoming instances of damage below %d to nothing for %d turns. %d%% of the negated damage is retained as Void Energy for your own use.
The damage threshold increases with your Spellpower.]])
:format(t.getThreshold(self, t), t.getDuration(self, t), t.getAbsorption(self, t) * 100)
end,
}
newTalent{
name = "Cancel",
short_name = "VSCHOL_REVERT",
type = {"celestial/vs-void", 3},
require = divi_req3,
points = 5,
cooldown = 18,
vschol_void = 20,
range = 6,
radius = 4,
tactical = { DISABLE = 3 },
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
getDuration = function(self, t)
return math.round(self:combatTalentLimit(t, 18, 3, 8))
end,
getGain = function(self, t)
return venergy_scale(self, t, 5)--self:combatTalentSpellDamage(t, 100, 10)
end,
createEff = function(self, t, x, y)
local e = Object.new{
name = self.name:capitalize() .. "'s cancellation field",
block_sight = false,
canAct = false,
x = x, y = y,
duration = t.getDuration(self,t),
gain = t.getGain(self, t),
summoner = self,
summoner_gain_exp = true,
act = function(self)
local Map = require "engine.Map"
self:useEnergy()
local actor = game.level.map(self.x, self.y, Map.ACTOR)
if actor then
local effs = {}
for eff_id, p in pairs(actor.tmp) do
local e = actor.tempeffect_def[eff_id]
if e.type ~= "other" and not e.subtype.void then
effs[#effs+1] = {"effect", eff_id}
end
end
if #effs > 0 then
local eff = rng.tableRemove(effs)
--if eff[1] == "effect" then
--actor:removeEffect(eff[2])
--end
actor:dispel(eff[2], self.summoner)
if actor:reactionToward(self.summoner) < 0 then
self.summoner:incVschol_void(self.gain)
end
end
end
if self.duration <= 0 then
-- remove
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
game.level.map:remove(self.x, self.y, Map.TERRAIN+3)
game.level:removeEntity(self, true)
--self.creepingDark = nil
game.level.map:scheduleRedisplay()
end
self.duration = self.duration-1
end,
}
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN+3, e)
-- add particles
e.particles = Particles.new("vschol_cancel", 1, {})
e.particles.x = x
e.particles.y = y
game.level.map:addParticleEmitter(e.particles)
e.energy.value = game.energy_to_act
end,
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)
self:project(tg, x, y, function(tx, ty)
t.createEff(self, t, tx, ty)
end)
game:playSoundNear(self, "talents/dispel")
return true
end,
info = function(self, t)
return ([[Purge a radius-4 area in degenerative Void energy, creating a field that removes one temporary effect per turn from all targets within for %d turns. You regain %0.1f Void Energy per effect removed from hostile targets. Void-subtype effects are unaffected.
Void Energy restoration improves with your Spellpower.]])
:format(t.getDuration(self, t), t.getGain(self, t))
end,
}
--If a projector is standing in an Abation field, block some damage
class:bindHook("DamageProjector:final", function (self, data)
if data.src and data.src.x then
local abation = game.level.map:checkAllEntities(data.src.x, data.src.y, "vschol_abate")
if abation then
local reduction = math.min(abation.reduction, data.dam)
data.dam = data.dam - reduction
game:delayedLogDamage(data.src, data.target, 0, ("#SLATE#(%d abated)#LAST#"):format(reduction), false)
end
end
return data
end)
newTalent{
name = "Abate",
short_name = "VSCHOL_ABATE",
type = {"celestial/vs-void", 4},
require = divi_req4,
points = 5,
cooldown = 24,
vschol_void = 20,
range = 6,
radius = 4,
tactical = { DISABLE = 3 },
target = function(self, t)
return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
end,
getDuration = function(self, t)
return math.round(self:combatTalentLimit(t, 24, 4, 8))
end,
getReduction = function(self, t)
return self:combatTalentSpellDamage(t, 5, 100)
end,
createEff = function(self, t, x, y)
local e = Object.new{
name = self.name:capitalize() .. "'s abation field",
block_sight = false,
canAct = false,
x = x, y = y,
duration = t.getDuration(self, t),
reduction = t.getReduction(self, t),
summoner = self,
summoner_gain_exp = true,
act = function(self)
local Map = require "engine.Map"
self:useEnergy()
if self.duration <= 0 then
-- remove
if self.particles then game.level.map:removeParticleEmitter(self.particles) end
game.level.map:remove(self.x, self.y, Map.TERRAIN+3)
game.level:removeEntity(self, true)
--self.creepingDark = nil
game.level.map:scheduleRedisplay()
end
self.duration = self.duration-1
end,
}
game.level:addEntity(e)
game.level.map(x, y, Map.TERRAIN+3, e)
e.vschol_abate = e
-- add particles
e.particles = Particles.new("vschol_abate", 1, {})
e.particles.x = x
e.particles.y = y
game.level.map:addParticleEmitter(e.particles)
e.energy.value = game.energy_to_act
end,
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)
self:project(tg, x, y, function(tx, ty)
t.createEff(self, t, tx, ty)
end)
game:playSoundNear(self, "talents/echo")
return true
end,
info = function(self, t)
return ([[Project a lingering radius-4 shroud of Void energy that diminishes the energy levels of all targets within for %d turns, reducing each damage instance they deal by %d.
Damage reduction increases with Spellpower.]])
:format(t.getDuration(self, t), t.getReduction(self, t))
end,
}
PK Y�Y �Y data/timed_eff.lua
local Particles = require "engine.Particles"
newEffect{
name = "VSCHOL_CIPHER_APRAXIS", image = "talents/vschol_logia_of_apraxis.png",
desc = "Apraxic Cipher",
long_desc = function(self, eff) return ("The target is receding away from physical contact, reducing their accuracy by %d."):format(eff.power) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10},
on_gain = function(self, err) return nil, "+Apraxis" end,
on_lose = function(self, err) return nil, "-Apraxis" end,
activate = function(self, eff)
self:effectTemporaryValue(eff, "combat_atk", -eff.power)
end,
deactivate = function(self, eff)
end,
}
newEffect{
name = "VSCHOL_CIPHER_DYSNOMY", image = "talents/vschol_logia_of_dysnomy.png",
desc = "Dysnomic Cipher",
long_desc = function(self, eff) return ("The target's perceptions have been warped, reducing their spellpower and mindpower by %d."):format(eff.power) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10},
on_gain = function(self, err) return nil, "+Dysnomy" end,
on_lose = function(self, err) return nil, "-Dysnomy" end,
activate = function(self, eff)
self:effectTemporaryValue(eff, "combat_spellpower", -eff.power)
self:effectTemporaryValue(eff, "combat_mindpower", -eff.power)
end,
deactivate = function(self, eff)
end,
}
newEffect{
name = "VSCHOL_CIPHER_ANTIPOSITION", image = "talents/vschol_logia_of_antiposition.png",
desc = "Antipositional Cipher",
long_desc = function(self, eff) return ("Distances are distorted for the target, reducing their movement speed by %d%%."):format(eff.power*100) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=.1},
on_gain = function(self, err) return nil, "+Antiposition" end,
on_lose = function(self, err) return nil, "-Antiposition" end,
activate = function(self, eff)
self:effectTemporaryValue(eff, "movement_speed", -eff.power)
end,
deactivate = function(self, eff)
end,
}
newEffect{
name = "VSCHOL_SIPHON", image = "talents/vschol_siphon.png",
desc = "Siphon",
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = { power=10 },
charges = function(self, eff) return math.floor(eff.power) end,
long_desc = function(self, eff)
return ("The target's energy has been drained, reducing their damage and healing modifier by %d%%."):format(math.round(eff.power))
end,
on_gain = function(self, err) return "#Target#'s energy has been drained!", "+Siphon" end,
on_lose = function(self, err) return "#Target# recovers their energy.", "-Siphon" end,
activate = function(self, eff)
eff.max_power = eff.max_power or eff.power * 2
eff.incdam_id = self:addTemporaryValue("inc_damage", { all = -eff.power })
eff.healmod_id = self:addTemporaryValue("healing_factor", -eff.power/100)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("inc_damage", eff.incdam_id)
self:removeTemporaryValue("healing_factor", eff.healmod_id)
end,
on_merge = function(self, old_eff, new_eff)
old_power = old_eff.power * old_eff.dur
new_power = new_eff.power * new_eff.dur
old_eff.max_power = math.max(old_eff.max_power, new_eff.max_power or 0)
old_eff.power = math.min(old_eff.max_power, (old_power + new_power) / new_eff.dur)
old_eff.dur = new_eff.dur
self:removeTemporaryValue("inc_damage", old_eff.incdam_id)
self:removeTemporaryValue("healing_factor", old_eff.healmod_id)
old_eff.incdam_id = self:addTemporaryValue("inc_damage", { all = -old_eff.power })
old_eff.healmod_id = self:addTemporaryValue("healing_factor", -old_eff.power/100)
return old_eff
end,
}
newEffect{
name = "VSCHOL_NULLIFY", image = "talents/vschol_nullify.png",
desc = "Nullification Shield",
long_desc = function(self, eff) return ("The target is shielded by Void energy, nullifying any incoming damage instances below %d. %d%% of the absorbed damage is converted into Void energy."):format(eff.power, eff.absorb*100) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "beneficial",
parameters = {power=10, absorb=.05},
--we want this to trigger after logia for synergy
callbackPriorities={callbackOnHit = 2},
charges = function(self, eff) return math.floor(eff.power) end,
on_gain = function(self, err) return "#Target# is surrounded by void energy!", "+Nullify" end,
on_lose = function(self, err) return "The void energy around #Target# subsides.", "-Nullify" end,
callbackOnHit = function(self, eff, cb, src, death_note)
if cb.value > eff.power then return end
self:incVschol_void(cb.value * eff.absorb)
game:delayedLogDamage(src, self, 0, ("#8b7fba#(%d nullified)#LAST#"):format(cb.value), false)
cb.value = 0
return cb
end,
activate = function(self, eff)
if core.shader.active(4) then
eff.particle = self:addParticles(Particles.new("shader_shield", 1, {img="shield7"}, {type="shield", shieldIntensity=0.3, color={0.075, 0.075, 0.1}}))
else
eff.particle = self:addParticles(Particles.new("damage_shield", 1))
end
end,
deactivate = function(self, eff)
self:removeParticles(eff.particle)
end,
}
newEffect{
name = "VSCHOL_RECOMBINATION", image = "talents/vschol_recombination.png",
desc = "Recombination",
long_desc = function(self, eff) return ("The target is surrounded by a nexus of Void flow, accruing %0.1f Void Energy each turn."):format(eff.power) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "beneficial",
parameters = {power=10},
callbackPriorities={callbackOnHit = 2},
charges = function(self, eff) return ("%0.1f"):format(eff.power) end,
on_gain = function(self, err) return "The Void surges around #Target#!", "+Recombine" end,
on_lose = function(self, err) return "The void surge around #Target# ends.", "-Recombine" end,
activate = function(self, eff)
self:effectTemporaryValue(eff, "vschol_void_regen", eff.power)
end,
deactivate = function(self, eff)
end,
}
local function qAnisotropy_CalcValues (self, eff)
eff.power_mult = eff.stacks ^ .5
eff.totalpower = eff.power * eff.power_mult
if eff.save_id then
self:removeTemporaryValue("combat_spellresist", eff.save_id.spell)
self:removeTemporaryValue("combat_mentalresist", eff.save_id.mind)
self:removeTemporaryValue("combat_physresist", eff.save_id.phys)
end
eff.save_id = {}
eff.save_id.spell = self:addTemporaryValue("combat_spellresist", -eff.totalpower)
eff.save_id.mind = self:addTemporaryValue("combat_mentalresist", -eff.totalpower)
eff.save_id.phys = self:addTemporaryValue("combat_physresist", -eff.totalpower)
end
newEffect{
name = "VSCHOL_QUANTUM_ANISOTROPY", image = "talents/vschol_quantum_anisotropy.png",
desc = "Quantum Anisotropy",
long_desc = function(self, eff) return ("The target is fractured by the Void's chaos, reducing their saves by %d. (Stacks: %d)"):format(eff.totalpower, eff.stacks) end,
type = "other", --other-type because otherwise this will easily overwhelm cleanses
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10, stacks=1},
charges = function(self, eff) return math.round(eff.totalpower) end,
activate = function(self, eff)
qAnisotropy_CalcValues(self, eff)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("combat_spellresist", eff.save_id.spell)
self:removeTemporaryValue("combat_mentalresist", eff.save_id.mind)
self:removeTemporaryValue("combat_physresist", eff.save_id.phys)
end,
on_merge = function(self, old_eff, new_eff)
old_eff.dur = math.max(old_eff.dur, new_eff.dur)
old_eff.power = math.max(old_eff.power, new_eff.power)
old_eff.stacks = old_eff.stacks + new_eff.stacks
qAnisotropy_CalcValues(self, old_eff)
return old_eff
end,
}
newEffect{
name = "VSCHOL_DESTABILISE", image = "talents/vschol_destabilise.png",
desc = "Destabilise",
long_desc = function(self, eff) return ("The target is crackling with unstable energy, causing any temporal damage sustained to trigger a discharge of %0.1f arcane damage."):format(eff.damage * eff.mult) end,
type = "magical",
subtype = { void=true },
status = "detrimental",
parameters = {damage=10, gain=1, triggers=1, mult=1},
charges = function(self, eff) return math.round(eff.damage * eff.mult) end,
on_gain = function(self, err) return "#Target# crackles with unstable energy!", "+Unstable" end,
on_lose = function(self, err) return "The unstable energy within #Target# dissipates.", "-Unstable" end,
--onHit so shields can block the effect
callbackOnHit = function(self, eff, cb, src, death_note)
if not death_note then return end
if not death_note.damtype then return end
local damtype = death_note.damtype
if damtype ~= 'TEMPORAL' then return end
if cb.value <= 0 then return end
DamageType:get(DamageType.ARCANE).projector(eff.src, self.x, self.y, DamageType.ARCANE, eff.damage * eff.mult)
eff.src:incVschol_void(eff.gain * eff.mult)
eff.triggers = eff.triggers+1
eff.mult = eff.triggers ^ .5 - (eff.triggers-1) ^ .5
--eff.mult = 1 / (eff.triggers ^ .5)
end,
}
local function degeneration_CalcValues (self, eff)
eff.totalpower = eff.power * eff.stacks
--Downshifting bonus
if eff.src and eff.src:knowTalent(eff.src.T_VSCHOL_DOWNSHIFTING) then
local tDown = eff.src:getTalentFromId(eff.src.T_VSCHOL_DOWNSHIFTING)
local num = 0
-- Count all our non-other detri effects
for eff_id, p in pairs(self.tmp) do
local e = self.tempeffect_def[eff_id]
if e.type ~= "other" and e.status == "detrimental" and e.name ~= "VSCHOL_DEGENERATION" then
num = num+1
end
end
if num >= tDown.effnum then
eff.totalpower = eff.totalpower * tDown.getMult(eff.src, tDown)
end
end
if eff.resist_id then
self:removeTemporaryValue("resists", eff.resist_id)
end
eff.resist_id = self:addTemporaryValue("resists", {TEMPORAL = -eff.totalpower})
if eff.particle then
self:removeParticles(eff.particle)
end
eff.particle = self:addParticles(Particles.new("vschol_degeneration", 1, {num = eff.stacks}))
end
newEffect{
name = "VSCHOL_DEGENERATION", image = "talents/vschol_degeneration.png",
desc = "Degeneration",
long_desc = function(self, eff) return ("The target is being crushed into nothing by void power, reducing their Temporal resistance by %d%%. (Stacks: %d)"):format(eff.totalpower, eff.stacks) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10, stacks=1},
charges = function(self, eff) return math.round(eff.totalpower) end,
--we recalc values when taking damage before resists so downshifting is always applied properly :)
callbackOnTakeDamageBeforeResists = function(self, eff)
degeneration_CalcValues(self, eff)
end,
--handle Emissive Accretion on timeout
on_timeout = function(self, eff)
if eff.stacks >= 2 and eff.src and eff.src:knowTalent(eff.src.T_VSCHOL_EMISSIVE_ACCRETION) then
local t = eff.src:getTalentFromId(eff.src.T_VSCHOL_EMISSIVE_ACCRETION)
for tid, cd in pairs(eff.src.talents_cd) do
local tt = eff.src:getTalentFromId(tid)
if tt.type[1]:find("^celestial/") then
if rng.percent(t.getChance(eff.src, t) * eff.stacks) then
eff.src:alterTalentCoolingdown(tid, -1)
end
end
end
end
end,
activate = function(self, eff)
degeneration_CalcValues(self, eff)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("resists", eff.resist_id)
if eff.particle then
self:removeParticles(eff.particle)
end
end,
on_merge = function(self, old_eff, new_eff)
old_eff.dur = math.max(old_eff.dur, new_eff.dur)
old_eff.power = math.max(old_eff.power, new_eff.power)
old_eff.stacks = math.min(old_eff.stacks + new_eff.stacks, 4)
degeneration_CalcValues(self, old_eff)
return old_eff
end,
}
newEffect{
name = "VSCHOL_DAMAGE_DIFFUSION", image = "talents/vschol_diffusion_field.png",
desc = "Damage Diffusion",
long_desc = function(self, eff) return ("The target is swirling with energy runoff, returning diffused damage at a rate of %0.1f per turn."):format(eff.power) end,
type = "other",
subtype = { void=true },
status = "detrimental",
parameters = {power=10},
charges = function(self, eff) return math.round(eff.power * eff.dur) end,
--drain life
on_timeout = function(self, eff)
--self.life = math.max(self.life - eff.power, self.die_at + 1)
--self.changed = true
local dead, dam = self:takeHit(eff.power, self, {vschol_damage_diffusion = true})
game:delayedLogDamage(eff, self, dam, ("#8b7fba#%d diffused#LAST#"):format(dam), false)
end,
activate = function(self, eff)
end,
deactivate = function(self, eff)
end,
on_merge = function(self, old_eff, new_eff)
local totaldam = (old_eff.power*(old_eff.dur)) + (new_eff.power*(new_eff.dur))
old_eff.dur = math.max(old_eff.dur, new_eff.dur)
old_eff.power = totaldam / (old_eff.dur)
return old_eff
end,
}
newEffect{
name = "VSCHOL_VOID_DISRUPTION", image = "talents/vschol_transposition.png",
desc = "Void Disruption",
long_desc = function(self, eff) return ("The target's power is bleeding out into hyperspace, reducing their Void Energy by %d%% each turn."):format(eff.power * 100) end,
type = "other",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10},
charges = function(self, eff) return ("%d%%"):format(eff.power * 100) end,
on_timeout = function(self, eff)
--self.life = math.max(self.life - eff.power, self.die_at + 1)
--self.changed = true
self:incVschol_void(-self.vschol_void * eff.power)
end,
activate = function(self, eff)
end,
deactivate = function(self, eff)
end,
}
local function cube_teleport (self, cube)
--find the closest valid tile within the cube's horizon
local closest_cube = nil
local closest_self = nil
local tiles = {}
cube.summoner:project({type="ball", start_x = cube.x, start_y = cube.y, radius=cube.event_horizon}, cube.x, cube.y, function(tx, ty)
--we only teleport to tiles we can move to as a compromise so we can justify allowing this in no-teleport areas
if self:hasLOS(tx, ty, "block_move", 100) and self:canMove(tx, ty) then
local dist_cube = core.fov.distance(cube.x, cube.y, tx, ty)
local dist_self = core.fov.distance(self.x, self.y, tx, ty)
closest_cube = closest_cube or dist_cube
if dist_cube <= closest_cube then
if dist_cube < closest_cube then
closest_cube = dist_cube
tiles={}
closest_self=nil
end
closest_self = closest_self or dist_self
if dist_self <= closest_self then
if dist_self < closest_self then
closest_self = dist_self
tiles = {}
end
tiles[#tiles+1] = {x=tx, y=ty}
end
end
end
end)
if #tiles > 0 then
local tile = rng.tableRemove(tiles)
game.level.map:particleEmitter(self.x, self.y, 1, "vschol_hypersphere_displace")
game.level.map:particleEmitter(tile.x, tile.y, 1, "vschol_hypersphere_displace")
self:move(tile.x, tile.y, true)
end
end
newEffect{
name = "VSCHOL_EVENT_HORIZON", image = "talents/vschol_event_horizon.png",
desc = "Event Horizon",
long_desc = function(self, eff) return ("The target is trapped in a Void bubble, and may be returned to the origin point each time it moves until it fully escapes the bubble's radius.") end,
type = "other",
subtype = { void=true },
status = "detrimental",
parameters = {power=10},
--charges = function(self, eff) return eff.power end,
--no timing out; the effect is removed when the target leaves its radius
decrease = 0,
callbackOnMove = function(self, eff, moved, forced, ox, oy, x, y)
if not ox then return end
local cubeFound = false
if self.x ~= ox or self.y ~= oy then
for i=1, #eff.cubes do
local cube = game.level.map:checkAllEntities(eff.cubes[i].x, eff.cubes[i].y, "vschol_hypercube")
if cube and cube.event_horizon and core.fov.distance(ox, oy, cube.x, cube.y) <= cube.event_horizon then
--game.log ("cube")
cubeFound = true
if rng.percent(cube.event_horizon_chance) and not self.turn_procs._vschol_event_horizon then
--game.log ("warp")
if self:canBe("teleport") and cube.summoner:checkHit(cube.summoner:combatSpellpower(), self:combatSpellResist() + (self:attr("continuum_destabilization") or 0), 0, 100, 0) then
--preventing recursive teleporting if we're caught between two cubes for whatever reason :)
self.turn_procs._vschol_event_horizon = true
cube_teleport(self, cube)
self.turn_procs._vschol_event_horizon = nil
end
end
end
end
end
-- if we didnt find a valid cube then we can clean up
if not cubeFound then
game:onTickEnd(function()
self:removeEffect(self.EFF_VSCHOL_EVENT_HORIZON)
end)
end
end,
activate = function(self, eff)
eff.cubes = {}
eff.cubes[1] = {x=eff.x, y=eff.y}
eff.x, eff.y = nil, nil
end,
deactivate = function(self, eff)
end,
on_merge = function(self, old_eff, new_eff)
old_eff.power = math.max(old_eff.power, new_eff.power)
local newCube = true
--check if the cube is already registered
for i=1, #old_eff.cubes do
if old_eff.cubes[i].x == new_eff.x and old_eff.cubes[i].y == new_eff.y then
newCube = nil
break
end
end
if newCube then
table.insert(old_eff.cubes, {x=x, y=y})
end
return old_eff
end,
}
--literally just a magic version of Slow with a merge function that isnt lunacy
newEffect{
name = "VSCHOL_ENRAPTURED", image = "talents/vschol_visage.png",
desc = "Enraptured",
long_desc = function(self, eff) return ("The target is mesmerised by the Void, reducing global action speed by %d%%."):format(math.floor(eff.power * 100)) end,
charges = function(self, eff) return (math.floor(eff.power * 100).."%") end,
type = "magical",
subtype = { slow=true, void=true },
status = "detrimental",
parameters = { power=0.1 },
on_gain = function(self, err) return "#Target# slows down.", "+Slow" end,
on_lose = function(self, err) return "#Target# speeds up.", "-Slow" end,
on_merge = function(self, old_eff, new_eff)
if new_eff.power > old_eff.power then
old_eff.power = new_eff.power
end
old_eff.dur = math.max(old_eff.dur, new_eff.dur)
return old_eff
end,
activate = function(self, eff)
eff.tmpid = self:addTemporaryValue("global_speed_add", -eff.power)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("global_speed_add", eff.tmpid)
end,
}
local function heatdeath_CalcValues(self, eff)
eff.totalpower = eff.power * eff.stacks
if eff.dmg_id then
self:removeTemporaryValue("numbed", eff.dmg_id)
end
eff.dmg_id = self:addTemporaryValue("numbed", eff.totalpower)
if eff.particle then
self:removeParticles(eff.particle)
end
eff.particle = self:addParticles(Particles.new("vschol_heatdeath", 1, {num = eff.stacks}))
end
newEffect{
name = "VSCHOL_HEAT_DEATH", image = "talents/vschol_heat_death.png",
desc = "Heat Death",
long_desc = function(self, eff) return ("The target's existence is blurring away into nothing, reducing its damage by %d%%. (Stacks: %d)"):format(eff.totalpower, eff.stacks) end,
type = "magical",
subtype = { void=true }, no_ct_effect = true,
status = "detrimental",
parameters = {power=10, stacks=1, max_stacks=4},
charges = function(self, eff) return math.round(eff.totalpower) end,
on_gain = function(self, err) return "#Target# feels the call of the Void!", "+Heat Death" end,
on_lose = function(self, err) return "#Target# returns to reality.", "-Heat Death" end,
activate = function(self, eff)
heatdeath_CalcValues(self, eff)
end,
deactivate = function(self, eff)
self:removeTemporaryValue("numbed", eff.dmg_id)
if eff.particle then
self:removeParticles(eff.particle)
end
end,
on_merge = function(self, old_eff, new_eff)
old_eff.dur = math.max(old_eff.dur, new_eff.dur)
old_eff.power = math.max(old_eff.power, new_eff.power)
old_eff.stacks = math.min(old_eff.stacks + new_eff.stacks, new_eff.max_stacks)
heatdeath_CalcValues(self, old_eff)
return old_eff
end,
}
-- Alter recall to hook in
local activate = TemporaryEffects.tempeffect_def.EFF_RECALL.activate
TemporaryEffects.tempeffect_def.EFF_RECALL.activate = function(self, eff)
activate(self, eff)
-- Only for people that did the actual void scholar start
if self.starting_quest ~= "voidscholar_class+start-voidscholar" then return end
local q = game.player:hasQuest("voidscholar_class+start-voidscholar")
-- Already returned
if not q or q:isCompleted("return") then return end
local q = game.player:hasQuest("starter-zones")
--have we completed all four t2's?
if q and q:isCompleted("old-forest") and q:isCompleted("daikara") and q:isCompleted("maze") and q:isCompleted("sandworm-lair") then
eff.where = "voidscholar_class+terminus-sanctum"
end
end
newEffect{
name = "VSCHOL_TELEPORT_TERMINUS", image = "talents/teleport_angolwen.png",
desc = _t"Teleport: Terminus Sanctum",
long_desc = function(self, eff) return _t"The target is constructing a Void portal to Terminus Sanctum." end,
type = "magical",
subtype = { teleport=true },
status = "beneficial",
cancel_on_level_change = true,
parameters = { },
activate = function(self, eff)
eff.leveid = game.zone.short_name.."-"..game.level.level
end,
deactivate = function(self, eff)
if self ~= game:getPlayer(true) then return end
local seen = false
-- Check for visible monsters, only see LOS actors, so telepathy wont prevent it
core.fov.calc_circle(self.x, self.y, game.level.map.w, game.level.map.h, 20, function(_, x, y) return game.level.map:opaque(x, y) end, function(_, x, y)
local actor = game.level.map(x, y, game.level.map.ACTOR)
if actor and actor ~= self then seen = true end
end, nil)
if seen then
game.log("There are creatures that could be watching you; you cannot take the risk of teleporting to Angolwen.")
return
end
if self:canBe("worldport") and not self:attr("never_move") and eff.dur <= 0 then
game:onTickEnd(function()
if eff.leveid == game.zone.short_name.."-"..game.level.level and game.player.can_change_zone then
game.logPlayer(self, "You are yanked out of this place!")
game:changeLevel(1, "voidscholar_class+terminus-sanctum")
end
end)
else
game.logPlayer(self, "Space restabilizes around you.")
end
end,
}PK ��,J J % data/zones/anomalous-sector/grids.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/grids/basic.lua")
load("/data/general/grids/void.lua")
load("/data/general/grids/cave.lua")
newEntity{
define_as = "RIFT",
name = "Next Sector", add_mos={{image="terrain/demon_portal2.png"}},
display = '&', color_r=255, color_g=0, color_b=220, back_color=colors.VIOLET,
notice = true,
always_remember = true,
show_tooltip = true,
desc=_t[[This rift leads deeper into the Anomalous Sector.]],
change_level = 1,
}
newEntity{
define_as = "RIFT_HOME",
name = "Temporal Rift", add_mos={{image="terrain/demon_portal2.png"}},
display = '&', color_r=255, color_g=0, color_b=220, back_color=colors.VIOLET,
notice = true,
always_remember = true,
show_tooltip = true,
desc=_t[[The rift leads to another part of the morass.]],
change_level = 1,
change_zone = "town-point-zero",
change_level_check = function()
game:onLevelLoad("town-point-zero-1", function(zone, level)
game:onTickEnd(function()
local npc = game.zone:makeEntityByName(level, "actor", "TEMPORAL_DEFILER", true)
local spot = level:pickSpot{type="pop", subtype="defiler"}
game.zone:addEntity(level, npc, "actor", spot.x, spot.y)
npc:setTarget(game.player)
local spot = level:pickSpot{type="pop", subtype="player-attack"}
game.player:move(spot.x, spot.y, true)
require("engine.ui.Dialog"):simpleLongPopup(_t"Point Zero", _t"The rift has brought you back to Point Zero, and the source of the disturbances.\nA temporal defiler is attacking the town, all the Keepers in range are attacking it!", 400)
for uid, e in pairs(game.level.entities) do
if e.faction == "keepers-of-reality" or e.faction == "point-zero-guardians" then
e:setEffect(e.EFF_KEEPER_OF_REALITY, 20, {})
end
end
end)
end)
end,
}
local rift_editer = { method="sandWalls_def", def="rift"}
newEntity{
define_as = "SPACETIME_RIFT",
type = "wall", subtype = "rift",
name = "crack in spacetime",
display = '#', color=colors.YELLOW, image="terrain/rift/rift_inner_05_01.png",
always_remember = true,
block_sight = true,
does_block_move = true,
_noalpha = false,
nice_editer = rift_editer,
}
PK ��r�� � $ data/zones/anomalous-sector/npcs.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
--load("/data/general/npcs/rodent.lua", rarity(5))
--load("/data/general/npcs/vermin.lua", rarity(2))
--load("/data/general/npcs/snake.lua", rarity(3))
--load("/data/general/npcs/bear.lua", rarity(2))
--load("/data/general/npcs/crystal.lua", rarity(10))
--load("/data/general/npcs/losgoroth.lua", rarity(nil))
--load("/data/general/npcs/all.lua", rarity(4, 35))
local Talents = require("engine.interface.ActorTalents")
newEntity{
define_as = "BASE_NPC_LOSGOROTH_FLAWED",
type = "elemental", subtype = "void",
blood_color = colors.DARK_GREY,
display = "E", color=colors.DARK_GREY,
desc = _t[[Losgoroth are mighty void elementals, native to the void between the stars. This one appears sickly, its edges fractured and crumbling.]],
faction = "cosmic-fauna",
combat = { dam=resolvers.levelup(resolvers.mbonus(50, 18), 1, 1.2), atk=15, apr=15, dammod={mag=0.8}, damtype=DamageType.ARCANE },
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
infravision = 10,
life_rating = 8,
life_regen = 0,
rank = 2,
size_category = 3,
levitation = 1,
can_pass = {pass_void=70},
autolevel = "dexmage",
ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=2, },
stats = { str=10, dex=8, mag=15, con=8 },
--no arcane immunity, temporal weakness
resists = { [DamageType.PHYSICAL] = -20, [DamageType.TEMPORAL] = -20 },
no_breath = 1,
poison_immune = 1,
disease_immune = 1,
cut_immune = 1,
blind_immune = 1,
confusion_immune = 1,
power_source = {arcane=true},
}
newEntity{ base = "BASE_NPC_LOSGOROTH_FLAWED",
name = "warped losgoroth", color=colors.GREY,
image = "npc/elemental_void_losgoroth.png",
level_range = {1, nil}, exp_worth = 1,
rarity = 1,
deep_rarity = 2,
max_life = resolvers.rngavg(40,60),
combat_armor = 0, combat_def = 20,
on_melee_hit = { [DamageType.ARCANE] = resolvers.mbonus(20, 10), },
life_rating = 8,
resolvers.talents{
[Talents.T_VOID_BLAST]={base=1, every=7, max=7},
},
}
newEntity{ base = "BASE_NPC_LOSGOROTH_FLAWED",
name = "deranged manaworm", color=colors.BLUE,
image = "npc/elemental_void_manaworm.png",
level_range = {2, nil}, exp_worth = 1,
desc = _t[[Manaworms are losgoroth which feed on the mana of arcane users. However, this one appears to have gone mad, and strikes not to feed, but kill and maim.]],
rarity = 2,
deep_rarity = 3,
autolevel = "warrior",
max_life = resolvers.rngavg(50,80),
movement_speed = .5,
life_rating = 10,
combat_armor = 0, combat_def = 20,
combat = { dam=resolvers.levelup(resolvers.mbonus(100, 30), 1, 1.2), atk=15, apr=15, dammod={mag=1}, damtype=DamageType.PHYSICAL },
resolvers.talents{
[Talents.T_RUSH]={base=3, every=7, max=4},
},
}
newEntity{ base = "BASE_NPC_LOSGOROTH_FLAWED",
name = "lithogoroth", color=colors.DARK_GREY,
image = "npc/vschol_lithogoroth.png",
level_range = {2, nil}, exp_worth = 1,
desc = _t[[Chunks of space debris appear to have been merged into this mutated losgoroth's body. Frenzied from pain, it ejects pieces of itself at anything in sight.]],
rarity = nil,
deep_rarity = 2,
max_life = resolvers.rngavg(65,100),
combat_armor = 20, combat_def = 0,
on_melee_hit = { [DamageType.ARCANE] = resolvers.mbonus(20, 10), },
life_rating = 10,
resists = { [DamageType.PHYSICAL] = 20, [DamageType.TEMPORAL] = -15 },
ai = "tactical",
ai_tactic = resolvers.tactic"ranged",
resolvers.talents{
[Talents.T_VSCHOL_ROCK_THROW]={base=1, every=7, max=7},
},
}
newEntity{ base = "BASE_NPC_LOSGOROTH_FLAWED",
name = "void maw", color=colors.RED,
image = "npc/vschol_void_maw.png",
level_range = {2, nil}, exp_worth = 1,
desc = _t[[A giant mutated manaworm that has partially embedded itself in the firmament of the Void. Space itself flows endlessly into its waiting maw, dragging its prey along with it.]],
rarity = nil,
deep_rarity = 3,
size_category = 4,
autolevel = "warrior",
never_move = 1,
max_life = resolvers.rngavg(75,100),
movement_speed = .5,
life_rating = 11,
combat_armor = 10, combat_def = 0,
resolvers.talents{
[Talents.T_VSCHOL_SWALLOW_SPACE]={base=1, every=7, max=4},
},
}
newEntity{ base="BASE_NPC_LOSGOROTH_FLAWED", define_as = "RAVENING_WORMHOLE",
unique = true,
name = "Ravening Wormhole",
color=colors.VIOLET,
image = "npc/vschol_ravenous_wormhole.png",
display_w = 2, display_h = 2, display_x = -0.5, display_y = -1,
--resolvers.nice_tile{image="invis.png", add_mos = {{image="npc/vschol_ravenous_wormhole.png", display_h=2, display_y=-1, display_w=2, display_x=-1}}},
desc = _t[[Countless losgoroths struggle and writhe against each other in a chaotic mass, their forms fracturing and merging. Through their broken bodies, you catch glimpses of a darkness within darkness at the center; a hole leading into an infinite abyss on the obverse of reality. You faintly hear a deep thrumming under the shrieks of the captured elementals, which puts you uncomfortably in mind of an animal panting and drooling.
You have to destroy it before it threatens the Sanctum, obviously.]],
killer_message = _t"and utterly engulfed",
level_range = {7, nil}, exp_worth = 2,
max_life = 150, life_rating = 10, fixed_rating = true,
stats = { str=10, dex=10, cun=12, mag=20, con=10 },
vschol_void_regen = 3,
rank = 4,
tier1 = true,
size_category = 5,
infravision = 10,
instakill_immune = 1,
can_pass = {pass_void=0},
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, LITE=1 },
equipment = resolvers.equip{
{defined="LIMINAL_SEED", autoreq=true},
},
resolvers.drops{chance=100, nb=3, {tome_drops="boss"} },
resists = { [DamageType.PHYSICAL] = 0, [DamageType.TEMPORAL] = -15 },
resolvers.talents{
[Talents.T_VSCHOL_DISRUPT]={base=1, every=7, max=7},
[Talents.T_VSCHOL_FILAMENT]={base=1, every=7, max=7},
[Talents.T_VSCHOL_DEGENERATION]={base=1, every=7, max=7},
[Talents.T_VSCHOL_LOGIA_OF_ANTIPOSITION]={base=1, every=7, max=7},
},
resolvers.inscriptions(1, {"blink rune"}),
autolevel = "caster",
ai = "tactical", ai_state = { talent_in=1, ai_move="move_astar", },
ai_tactic = resolvers.tactic"ranged",
on_die = function(self, who)
--local q = game.player:hasQuest("start-archmage")
--if q then q:stabilized() end
--game.player:resolveSource():setQuestStatus("start-archmage", engine.Quest.COMPLETED, "abashed")
local chat = require("engine.Chat").new("voidscholar_class+wormhole-collapse", self, who, {player=who})
chat:invoke()
end,
}
PK fNm/ ' data/zones/anomalous-sector/objects.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/objects/objects-maj-eyal.lua")
local Talents = require "engine.interface.ActorTalents"
--basically Void Star but I wanted to make it a bit more unique
newEntity{ base = "BASE_LITE", define_as = "LIMINAL_SEED",
power_source = {arcane=true, unknown = true},
unique = true,
special = true,
name = "Liminal Seed", image="object/artifact/vschol_liminal_seed.png",
unided_name = _t"faintly-glowing shard",
level_range = {1, 10},
color = colors.GREY,
encumber = 1,
rarity = false,
desc = _t[[A tiny pitch-black fragment that pulses with pure Void energy. Deep within it -- deeper than would seem possible given its size -- you sense a trace of some power not of this universe.]],
cost = 120,
material_level = 2,
wielder = {
--combat_spellcrit = 5,
inc_damage = {
[DamageType.ARCANE] = 8,
[DamageType.TEMPORAL] = 8,
},
lite = 3,
vschol_void_regen = 1.5,
},
max_power = 70, power_regen = 1,
use_talent = { id = Talents.T_ECHOES_FROM_THE_VOID, level = 2, power = 70 },
}
PK �
��7 7 % data/zones/anomalous-sector/traps.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/traps/natural_forest.lua")
PK ��"� � $ data/zones/anomalous-sector/zone.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
return {
name = _t"Anomalous Sector",
level_range = {1, 5},
level_scheme = "player",
max_level = 3,
decay = {300, 800},
actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
width = 50, height = 50,
-- all_remembered = true,
all_lited = true,
tier1 = true,
persistent = "zone",
ambient_music = "Suspicion.ogg",
max_material_level = 1,
color_shown = {0.6, 0.5, 0.8, 1},
color_obscure = {0.6*0.6, 0.5*0.6, 0.8*0.6, 0.6},
generator = {
map = {
class = "engine.generator.map.Forest",
edge_entrances = {4,6},
zoom = 7,
sqrt_percent = 45,
sqrt_percent2 = 25,
noise = "fbm_perlin",
floor = "VOID",
wall = "CAVEWALL",
up = "VOID",
down = "RIFT",
},
actor = {
class = "mod.class.generator.actor.Random",
nb_npc = {20, 30},
filters = { {max_ood=2}, },
guardian = "RAVENING_WORMHOLE",
},
object = {
class = "engine.generator.object.Random",
--might be a bit too rewarding for an extra starter zone?
nb_object = {4, 8},
},
trap = {
class = "engine.generator.trap.Random",
nb_trap = {0, 0},
},
},
levels =
{
[2] = {
generator = {
actor = {
filters = {{special_rarity="deep_rarity"}},
},
},
},
[3] = {
generator = {
map = {
width = 40, height = 40,
class = "engine.generator.map.Forest",
edge_entrances = {4,6},
zoom = 7,
sqrt_percent = 35,
sqrt_percent2 = 25,
noise = "fbm_perlin",
floor = "VOID",
wall = "CAVEWALL",
up = "VOID",
down = "RIFT",
},
actor = {
filters = {{special_rarity="deep_rarity"}},
},
},
},
},
post_process = function(level)
local Map = require "engine.Map"
local Quadratic = require "engine.Quadratic"
if core.shader.allow("volumetric") then
level.starfield_shader = require("engine.Shader").new("starfield", {size={Map.viewport.width, Map.viewport.height}})
else
level.background_particle1 = require("engine.Particles").new("starfield_static", 1, {width=Map.viewport.width, height=Map.viewport.height, nb=300, a_min=0.5, a_max = 0.8, size_min = 1, size_max = 3})
level.background_particle2 = require("engine.Particles").new("starfield_static", 1, {width=Map.viewport.width, height=Map.viewport.height, nb=300, a_min=0.5, a_max = 0.9, size_min = 4, size_max = 8})
end
level.world_sphere = Quadratic.new()
game.zone.world_sphere_rot = (game.zone.world_sphere_rot or 0)
game.zone.cloud_sphere_rot = (game.zone.world_cloud_rot or 0)
end,
background = function(level, x, y, nb_keyframes)
local Map = require "engine.Map"
if level.starfield_shader and level.starfield_shader.shad then
level.starfield_shader.shad:use(true)
core.display.drawQuad(x, y, Map.viewport.width, Map.viewport.height, 1, 1, 1, 1)
level.starfield_shader.shad:use(false)
elseif level.background_particle then
level.background_particle.ps:toScreen(x, y, true, 1)
end
end,
foreground = function(level, x, y, nb_keyframes)
if not config.settings.tome.weather_effects or not level.foreground_particle then return end
level.foreground_particle.ps:toScreen(x, y, true, 1)
end,
on_enter = function(lev, old_lev, newzone)
game.player:attr("planetary_orbit", 1)
local Dialog = require("engine.ui.Dialog")
--if lev == 1 and not game.level.shown_warning then
if not game.level.shown_lore then
game.level.shown_lore = true
game.party:learnLore("vschol-sector" .. tostring(lev))
end
--elseif lev == 3 and not game.level.shown_warning and not game.level.data.is_flooded then
--game.level.shown_warning = true
--elseif lev == 3 and not game.level.shown_warning and game.level.data.is_flooded then
--Dialog:simpleLongPopup(_t"Lake of Nur", _t"As you descend to the next level you traverse a kind of magical barrier keeping the water away. The barrier seems to be failing however and the next level is flooded too.", 400)
--game.level.shown_warning = true
--end
end,
}
PK �7 � � % data/zones/terminus-sanctum/grids.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/grids/basic.lua")
load("/data/general/grids/forest.lua")
load("/data/general/grids/water.lua")
load("/data/general/grids/void.lua")
load("/data/general/grids/mountain.lua")
newEntity{
define_as = "RIFT",
name = "Void Portal to Eyal", image="terrain/floating_rocks05_01.png", add_mos={{image="terrain/demon_portal2.png"}},
display = '&', color_r=255, color_g=0, color_b=220, back_color=colors.VIOLET,
notice = true,
always_remember = true,
show_tooltip = true,
desc=_t[[A portal leading to the surface of Eyal.]],
--change_level = 1,
--change_zone = "wilderness",
--need to conditionally activate this
}
local ice_editer = {method="borders_def", def="ice"}
newEntity{
define_as = "COLD_FOREST",
type = "wall", subtype = "ice",
name = "cold forest", image = "terrain/tree_dark_snow1.png",
display = '#', color=colors.WHITE, back_color=colors.LIGHT_UMBER,
always_remember = true,
can_pass = {pass_tree=1},
does_block_move = true,
block_sight = true,
nice_tiler = { method="replace", base={"COLD_FOREST", 100, 1, 30} },
nice_editer = ice_editer,
}
for i = 1, 30 do
newEntity{ base="COLD_FOREST",
define_as = "COLD_FOREST"..i,
image = "terrain/frozen_ground.png",
add_displays = class:makeTrees("terrain/tree_dark_snow", 13, 10),
nice_tiler = false,
}
end
newEntity{
define_as = "POLAR_CAP",
type = "floor", subtype = "ice",
name = "polar cap", image = "terrain/frozen_ground.png",
display = '.', color=colors.LIGHT_BLUE, back_color=colors.WHITE,
can_encounter=true, equilibrium_level=-10,
nice_editer = ice_editer,
}
newEntity{
base="HARDWALL",
define_as = "SPACE_WALL",
type = "wall", subtype = "rocks",
}
PK ڶ��� � $ data/zones/terminus-sanctum/npcs.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
local Talents = require("engine.interface.ActorTalents")
newEntity{
define_as = "BASE_NPC_TERMINUS_SANCTUM",
type = "humanoid", subtype = "human",
display = "p", color=colors.PURPLE,
faction = "allied-kingdoms",
anger_emote = _t"Catch @himher@!",
hates_antimagic = 1,
--never_anger = 1,
combat = { dam=resolvers.rngavg(1,2), atk=2, apr=0, dammod={str=0.4} },
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
lite = 3,
life_rating = 10,
rank = 2,
size_category = 3,
open_door = true,
resolvers.racial(),
autolevel = "caster",
ai = "dumb_talented_simple", ai_state = { ai_move="move_complex", talent_in=3, },
stats = { str=6, dex=8, mag=12, con=10 },
}
newEntity{ base = "BASE_NPC_TERMINUS_SANCTUM", define_as = "VSCHOL_SCRIBE",
name = "void scribe", color=colors.YELLOW,
image = "npc/vschol_scribe.png",
female = resolvers.rngtable{false, true},
subtype = resolvers.rngtable{"human", "shalore"},
desc = _t[[A dark-robed Void Scholar tasked with compiling and interpreting the revelations gleaned by the Stargazers.]],
level_range = {1, nil}, exp_worth = 0,
rarity = 1,
combat_armor = 0, combat_def = 0,
}
newEntity{ base = "BASE_NPC_TERMINUS_SANCTUM", define_as = "VSCHOL_SCRIBE",
name = "void stargazer", color=colors.YELLOW,
image = "npc/vschol_stargazer.png",
female = resolvers.rngtable{false, true},
subtype = resolvers.rngtable{"human", "shalore"},
desc = _t[[A dark-robed Void Scholar peering into a glyph showing secrets from the deep Void.]],
level_range = {1, nil}, exp_worth = 0,
rarity = 1,
combat_armor = 0, combat_def = 0,
}
newEntity{ base = "BASE_NPC_TERMINUS_SANCTUM", define_as = "HEADMASTER_WRYLL",
name = "Great Headmaster Wryll", color=colors.VIOLET, unique = true,
image = "npc/vschol_headmaster.png",
subtype = "unknown",
desc = _t[[A pallid, shadowy figure dressed in magnificent robes coruscating with pure Void Energy. You know little about the infinitely-wise leader of the Void Scholars; their age, race, and background are known to very few, if any, and their gray, lifeless features betray little. Their thirst for the mysteries of the cosmos is absolutely unstoppable, their knowledge of its secrets unfathomable; their disciples revere them with awe as they follow them to their promised station as the sole lords of the universe.]],
level_range = {50, nil}, exp_worth = 1,
rarity = false,
max_life = 2000, life_rating = 20,
life_regen = 50,
--virtually limitless void energy
vschol_void_regen_global = 50,
faction = "allied-kingdoms",
can_talk = "voidscholar_class+wryll",
ai = "tactical", ai_state = { talent_in=1, ai_move="move_astar", },
ai_tactic = resolvers.tactic"ranged",
resolvers.inscriptions(5, {}),
resolvers.inscriptions(1, "rune"),
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
resolvers.drops{chance=100, nb=5, {tome_drops="boss"} },
combat_spellcrit = 70,
combat_spellpower = 60,
inc_damage = {all=80},
resists = {[DamageType.TEMPORAL]=100, [DamageType.ARCANE]=100},
combat_spellresist = 250,
combat_mentalresist = 250,
combat_physresist = 250,
resolvers.equip{
{type="weapon", subtype="staff", autoreq=true, forbid_power_source={antimagic=true}, tome_drops="boss"},
{type="armor", subtype="cloth", autoreq=true, forbid_power_source={antimagic=true}, tome_drops="boss"},
},
talent_cd_reduction = {all=23},
resolvers.talents{
[Talents.T_AETHER_PERMEATION]=1,
[Talents.T_ETHEREAL_FORM]=1,
--[Talents.T_MASTER_OF_DISASTERS]=1,
[Talents.T_METEORIC_CRASH]=1,
[Talents.T_ENDLESS_WOES]=1,
[Talents.T_EYE_OF_THE_TIGER]=1,
--[[[Talents.T_RETHREAD]=5,
[Talents.T_TEMPORAL_FUGUE]=5,
[Talents.T_SPACETIME_STABILITY]=5,
[Talents.T_STOP]=5,
[Talents.T_CHRONO_TIME_SHIELD]=5,
[Talents.T_STATIC_HISTORY]=5,
[Talents.T_CELERITY]=5,
[Talents.T_TIME_DILATION]=5,
[Talents.T_HASTE]=5,
[Talents.T_REPULSION_BLAST]=5,
[Talents.T_GRAVITY_SPIKE]=5,
[Talents.T_GRAVITY_LOCUS]=5,
[Talents.T_GRAVITY_WELL]=5,
[Talents.T_ENTROPY]=5,
[Talents.T_ENERGY_ABSORPTION]=5,
[Talents.T_REDUX]=5,]]
[Talents.T_VSCHOL_FILAMENT] = 5,
[Talents.T_VSCHOL_DEGENERATION] = 5,
[Talents.T_VSCHOL_ANTI_SINGULARITY] = 5,
[Talents.T_VSCHOL_EQUALISE] = 5,
[Talents.T_VSCHOL_REGRESS] = 5,
[Talents.T_VSCHOL_DIFFUSION_FIELD] = 5,
[Talents.T_VSCHOL_BLACKBODY_RADIATION] = 5,
[Talents.T_VSCHOL_LOGIA_OF_DYSNOMY] = 5,
[Talents.T_VSCHOL_LOGIA_CIPHER] = 5,
[Talents.T_VSCHOL_LOGIA_SAVANT] = 5,
[Talents.T_VSCHOL_LOGIA_ENIGMA] = 5,
[Talents.T_VSCHOL_TERMINATION] = 5,
[Talents.T_VSCHOL_EX_NIHILO] = 5,
[Talents.T_VSCHOL_VISAGE] = 5,
[Talents.T_VSCHOL_MEGALOTHEOS] = 5,
[Talents.T_VSCHOL_SIPHON] = 5,
[Talents.T_VSCHOL_NULLIFY] = 5,
[Talents.T_VSCHOL_COMPRESSED_SPACE] = 5,
[Talents.T_VSCHOL_HEAT_DEATH] = 5,
},
resolvers.sustains_at_birth(),
}PK �2 2 ' data/zones/terminus-sanctum/objects.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/objects/objects-maj-eyal.lua")
local Stats = require "engine.interface.ActorStats"
local Talents = require "engine.interface.ActorTalents"
newEntity{ base = "BASE_TOOL_MISC",
power_source = {arcane=true},
define_as = "TIME_SHARD",
desc = _t[[An iridescent shard of violet crystal. Its light ebbs and flows, sometimes fast and sometimes slow, keeping pace with the chaotic streams of time itself. It makes you feel both old and young, a newborn child and an ancient being, your flesh simply one instance in a thousand refractions of a single timeless and eternal soul.]],
unique = true,
name = "Shard of Crystalized Time", color = colors.YELLOW,
unided_name = _t"glowing shard", image = "object/artifact/time_shard.png",
level_range = {5, 12},
rarity = false,
cost = 10,
material_level = 1,
metallic = false,
wielder = {
inc_stats = { [Stats.STAT_WIL] = 4, [Stats.STAT_CON] = 2, },
combat_def = 5,
inc_damage = { [DamageType.TEMPORAL] = 7 },
paradox_reduce_anomalies = 10,
},
}
PK
�ыV
V
% data/zones/terminus-sanctum/traps.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
load("/data/general/traps/store.lua")
newEntity{ base = "BASE_STORE", define_as = "CLOTH_ARMOR_STORE",
name="Tailor",
display='2', color=colors.RED,
resolvers.store("CLOTH_ARMOR", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_tailor.png"),
}
newEntity{ base = "BASE_STORE", define_as = "LIGHT_ARMOR_STORE",
name="Tanner",
display='2', color=colors.UMBER,
resolvers.store("LIGHT_ARMOR", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_tanner.png"),
}
newEntity{ base = "BASE_STORE", define_as = "KNIFE_WEAPON_STORE",
name="Knives and daggers",
display='3', color=colors.UMBER,
resolvers.store("KNIFE_WEAPON", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_knives.png"),
}
newEntity{ base = "BASE_STORE", define_as = "ARCHER_WEAPON_STORE",
name="Death from Afar",
display='3', color=colors.UMBER,
resolvers.store("ARCHER_WEAPON", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_bows.png"),
}
newEntity{ base = "BASE_STORE", define_as = "SWORD_WEAPON_STORE",
name="Swordsmith",
display='3', color=colors.UMBER,
resolvers.store("SWORD_WEAPON", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_swordsmith.png"),
}
newEntity{ base = "BASE_STORE", define_as = "STAFF_WEAPON_STORE",
name="Staff carver",
display='3', color=colors.RED,
resolvers.store("STAFF_WEAPON", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_staves.png"),
}
newEntity{ base = "BASE_STORE", define_as = "RUNEMASTER",
name="Runemaster",
display='5', color=colors.RED,
resolvers.store("SCROLL", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_alchemist.png"),
}
newEntity{ base = "BASE_STORE", define_as = "JEWELRY",
name="Jewelry",
display='9', color=colors.LIGHT_RED,
resolvers.store("GEMSTORE", "keepers-of-reality", "store/shop_door.png", "store/shop_sign_jewelry.png"),
}
PK ��u[�
�
$ data/zones/terminus-sanctum/zone.lua-- 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 .
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
return {
name = _t"Terminus Sanctum",
level_range = {1, 15},
level_scheme = "player",
actor_adjust_level = function(zone, level, e) return zone.base_level + e:getRankLevelAdjust() + level.level-1 + rng.range(-1,2) end,
update_base_level_on_enter = true,
max_level = 1,
width = 50, height = 50,
decay = {300, 800, only={object=true}, no_respawn=true},
persistent = "zone",
all_remembered = true,
all_lited = true,
--day_night = true,
ambient_music = {"The Ancients.ogg"},
color_shown = {0.8, 0.5, 0.8, 1},
color_obscure = {0.8*0.6, 0.5*0.6, 0.8*0.6, 0.6},
allow_respec = "limited",
--effects = {"EFF_ZONE_AURA_OUT_OF_TIME"},
no_level_connectivity = true,
max_material_level = 2,
store_levels_by_restock = { 10, 20, 35, 45, 50, 60 },
generator = {
map = {
class = "engine.generator.map.Static",
map = "towns/vschol_terminus-sanctum",
},
actor = {
class = "mod.class.generator.actor.Random",
nb_npc = {8, 8},
},
object = {
class = "engine.generator.object.Random",
nb_object = {0, 0},
},
},
on_enter = function(lev, old_lev, newzone)
local q = game.player:hasQuest("voidscholar_class+start-voidscholar")
if q and q:isCompleted("anomalous-sector") and not q:isCompleted("return") then
game.player:setQuestStatus("voidscholar_class+start-voidscholar", engine.Quest.COMPLETED, "return")
game.player:move(24, 5, true)
local wryll = nil
--heehee that rhymes :D
game.level.map:applyAll(function(x, y, key, e)
if e.name == "Great Headmaster Wryll" then
wryll = e
end
end)
--we need to account for if the player SOMEHOW killed Wryll :V
if wryll then
local Chat = require("engine.Chat")
local chat = Chat.new("voidscholar_class+wryll-return", wryll, game.player)
chat:invoke()
end
game.player:learnTalent(game.player.T_VSCHOL_TELEPORT_TERMINUS, true, nil, {no_unlearn=true})
end
end,
post_process = function(level)
-- Cosmetic stuff
local Map = require "engine.Map"
if core.shader.allow("volumetric") then
level.starfield_shader = require("engine.Shader").new("starfield", {size={Map.viewport.width, Map.viewport.height}})
else
level.background_particle = require("engine.Particles").new("starfield", 1, {width=Map.viewport.width, height=Map.viewport.height})
end
end,
background = function(level, x, y, nb_keyframes)
local Map = require "engine.Map"
if level.starfield_shader and level.starfield_shader.shad then
level.starfield_shader.shad:use(true)
core.display.drawQuad(x, y, Map.viewport.width, Map.viewport.height, 1, 1, 1, 1)
level.starfield_shader.shad:use(false)
elseif level.background_particle then
level.background_particle.ps:toScreen(x, y, true, 1)
end
end,
}
PK a��� � hooks/load.lualocal class = require "engine.class"
local ActorTalents = require "engine.interface.ActorTalents"
local DamageType = require "engine.DamageType"
local Birther = require "engine.Birther"
local ActorTemporaryEffects = require "engine.interface.ActorTemporaryEffects"
local PartyLore = require "mod.class.interface.PartyLore"
local ActorResource = require "engine.interface.ActorResource"
class:bindHook("ToME:load", function (self, data)
--DamageType:loadDefinition("/data-heartstalker_class/damage_types.lua")
ActorTalents:loadDefinition("/data-voidscholar_class/talents/celestial.lua")
Birther:loadDefinition("/data-voidscholar_class/classdef.lua")
ActorTemporaryEffects:loadDefinition("/data-voidscholar_class/timed_eff.lua")
PartyLore:loadDefinition("/data-voidscholar_class/lore.lua")
--void energy
--a lot of things in here are decoupled from the standard handling so as to save me from having to superload 500000000 files
ActorResource:defineResource("Void energy", "vschol_void", ActorTalents.T_VSCHOL_VOID_POOL, "vschol_void_regen_global", "Void energy represents your reserve of unstable void power, spawned from the emptiness between celestial bodies. It disperses rapidly when out of combat.", 0, 50, {
color = "#8b7fba#",
randomboss_enhanced = true,
wait_on_rest = false,
cost_factor = function(self, t, check, value) if value < 0 then return 1 else return (100 + self:combatFatigue()) / 100 end end,
Minimalist = {highlight = function(player, vc, vn, vm, vr) return vc >=0.7*vm end},
--status_text = function(act)
--local vPool = act:getTalentFromId(act.T_MRFROG_VOID_POOL)
--local regen = vPool.getRegen(act, t)
--if regen and regen ~= 0 then
--return tostring(regen)
--else return "" end
--end,
})
end)
class:bindHook("Entity:loadList", function (self, data)
if data.file == "/data/zones/unremarkable-cave/npcs.lua" then
--showTable(data.res)
--showTable(data.loaded)
if data.res.FILLAREL then
--game.log(data.res.FILLAREL.can_talk)
if game.player.descriptor.subclass == "Void Scholar" then
data.res.FILLAREL.can_talk = "voidscholar_class+unremarkable-cave-fillarel"
end
end
end
end)
class:bindHook("Object:descWielder", function(self, data)
data.compare_fields(data.w, data.compare_with, data.field, "vschol_void_regen", "%+.2f", _t"V.Energy per turn (in-combat): ")
data.compare_fields(data.w, data.compare_with, data.field, "vschol_void_regen_global", "%+.2f", _t"V.Energy per turn: ")
end)PK ;�) � � init.lualong_name = "Void Scholar - Celestial Class"
short_name = "voidscholar_class"
description = [[Adds the Void Scholar, a twisted Celestial class themed around cosmic expansion and the emptiness of space, featuring a unique starting scenario. Void Scholars are dedicated spellcasters with poor durability, but many ways to negate damage and debilitate foes. Their spells are ineffective against nearby targets, but grow incredibly potent when cast at a distance -- strike from the depths of infinity, where no eyes or blades can reach! They use a unique resource called "Void Energy" that is built up by casting certain spells and quickly disperses when out of combat, requiring careful management to retain momentum.
Unique talent trees:
Logia (generic): Three forbidden chants to turn away slings, arrows, and everything else!
Void (generic): Render foes impotent before the majesty of the Void!
Vacuum: Draw upon the cosmic vacuum to replenish your resources!
Dilation: Pull space apart like putty to shred your foes!
Chaos: Crumble away your foes' defenses with the Void's power!
Collapse: Slowly crush enemies down to nothing under the Void's relentless weight!
Subspace: Ride and shape the Void to control the battlefield!
Dissolution: Dissolve away the material world to erode the strength of those who oppose you!
Genesis (locked): Repaint the world in your image to build overwhelming power!
Hyperspace (locked): Stretch and bend the universe to resculpt existence and unleash untold destruction!
Terminus (locked): Bring the slow, inevitable doom of the universe upon your foes!
All things come to an end, adventurer. Those who place themselves above the slow march of the cosmos should take heed not to stand upon nothing.]]
tags = {'class', 'celestial', 'void-scholar'}
author = {'Mr Frog'}
weight = 100
for_module = "tome"
version = {1, 7, 0}
addon_version = {1, 2, 4}
overload = true
superload = true
data = true
hooks = truePK �� Eԧ ԧ 5 overload/data/gfx/class-icons/void_scholar_128_bg.png�PNG
IHDR � � �>a� *�zTXtRaw profile type exif xڭ�i�9���cZ Ǹ�fځ��� 2Y����I2�d��1�����Hw������_o�\ʵ�^���S��/�����>�?���������|#��}�,������_���������~��~���g:���y_�9������o�>7��n��w\?��9�?��*��3dz��`�?��b���k���7+|m�ޟ�߯����?��W�X;?~^�]
����?��������9M��+
��_��[�����m��������H��*��~o�}�'Ki�c�_�ߙ�����ո�E�6ќ�Z.�Y�R�a���{��%�xb��W��Z�{\�$�
7V�������r�s-���8��3������/���^�n��Y+�+*k�EN�.�Ϛ淾��[���ֈ`~�ܸ���w���_�e/����O����9 KĹ3C^��K�J�5��؈��ʣ�8�@�9��.�1+�E������s�^ZD�P*��6VJ�����C#[N.�\r�-�<��Tr)�aԨVS͵�Z[�u�_��VZm��6z���^zu����ःC>=x�3N�i�Yf�m�9��ʫ����k�mS����v�}��t�ɧ�z��g\r��M7�r�m���'j?Q�ר�D�?G-�DMK�}����r����������� 3�BJQ�S�|�E�D-dgE��b��O���������*nS��D�)t?���q�7Q��1�� �
���.�v}>7�Uڵ�@�4�I�p ����g�s�YK���T��.��8�5�[Pm���#銣���{�w9�w[��\�μ�y����ʟ��<��۷�T�}�X|*R��Z暢Nuf=q�ivƮ�u;��<}����>�sH3�s!6�Z���|�����A�ʶia�������1Y�]�v�?