Forked from
tome / Tales of MajEyal
4993 commits behind the upstream repository.
-
Samuel Wegner authoredSamuel Wegner authored
chronomancer.lua 16.51 KiB
-- ToME - Tales of Maj'Eyal
-- Copyright (C) 2009 - 2015 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
-- Class Trees
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/blade-threading", name = "Blade Threading", description = "A blend of chronomancy and dual-weapon combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/bow-threading", name = "Bow Threading", description = "A blend of chronomancy and ranged combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/temporal-combat", name = "Temporal Combat", description = "A blend of chronomancy and physical combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/guardian", name = "Temporal Guardian", description = "Warden combat training and techniques." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/threaded-combat", name = "Threaded Combat", min_lev = 10, description = "A blend of ranged and dual-weapon combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/temporal-hounds", name = "Temporal Hounds", min_lev = 10, description = "Call temporal hounds to aid you in combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/flux", name = "flux", description = "Fluctuate spacetime." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/gravity", name = "gravity", description = "Call upon the force of gravity to crush, push, and pull your foes." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/matter", name = "matter", description = "Change and shape matter itself." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/spacetime-folding", name = "Spacetime Folding", description = "Mastery of folding points in space." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/speed-control", name = "Speed Control", description = "Control how fast objects and creatures move through spacetime." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/stasis", name = "stasis", description = "Stabilize spacetime." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/timeline-threading", name = "Timeline Threading", min_lev = 10, description = "Examine and alter the timelines that make up the spacetime continuum." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/timetravel", name = "timetravel", description = "Directly manipulate the flow of time" }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/spellbinding", name = "Spellbinding", min_lev = 10, description = "Manipulate chronomantic spells." }
-- Generic Chronomancy
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/chronomancy", name = "Chronomancy", generic = true, description = "Allows you to glimpse the future, or become more aware of the present." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/energy", name = "energy", generic = true, description = "Manipulate raw energy by addition or subtraction." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/fate-weaving", name = "Fate Weaving", generic = true, description = "Weave the threads of fate." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/spacetime-weaving", name = "Spacetime Weaving", generic = true, description = "Weave the threads of spacetime." }
-- Misc and Outdated Trees
newTalentType{ no_silence=true, is_spell=true, type="chronomancy/manifold", name = "Manifold", generic = true, description = "Passive effects that Weapon Folding can trigger." }
newTalentType{ no_silence=true, is_spell=true, type="chronomancy/other", name = "Other", generic = true, description = "Miscellaneous Chronomancy effects." }
newTalentType{ no_silence=true, is_spell=true, type="chronomancy/age-manipulation", name = "Age Manipulation", description = "Manipulate the age of creatures you encounter." }
newTalentType{ no_silence=true, is_spell=true, type="chronomancy/temporal-archery", name = "Temporal Archery", description = "A blend of chronomancy and ranged combat." }
newTalentType{ allow_random=true, no_silence=true, is_spell=true, type="chronomancy/paradox", name = "paradox", description = "Break the laws of spacetime." }
-- Anomalies are not learnable but can occur instead of an intended spell when paradox gets to high.
newTalentType{ no_silence=true, is_spell=true, type="chronomancy/anomalies", name = "anomalies", description = "Spacetime anomalies that can randomly occur when paradox is to high." }
-- Generic requires for chronomancy spells based on talent level
chrono_req1 = {
stat = { mag=function(level) return 12 + (level-1) * 2 end },
level = function(level) return 0 + (level-1) end,
}
chrono_req2 = {
stat = { mag=function(level) return 20 + (level-1) * 2 end },
level = function(level) return 4 + (level-1) end,
}
chrono_req3 = {
stat = { mag=function(level) return 28 + (level-1) * 2 end },
level = function(level) return 8 + (level-1) end,
}
chrono_req4 = {
stat = { mag=function(level) return 36 + (level-1) * 2 end },
level = function(level) return 12 + (level-1) end,
}
chrono_req5 = {
stat = { mag=function(level) return 44 + (level-1) * 2 end },
level = function(level) return 16 + (level-1) end,
}
chrono_req_high1 = {
stat = { mag=function(level) return 22 + (level-1) * 2 end },
level = function(level) return 10 + (level-1) end,
}
chrono_req_high2 = {
stat = { mag=function(level) return 30 + (level-1) * 2 end },
level = function(level) return 14 + (level-1) end,
}
chrono_req_high3 = {
stat = { mag=function(level) return 38 + (level-1) * 2 end },
level = function(level) return 18 + (level-1) end,
}
chrono_req_high4 = {
stat = { mag=function(level) return 46 + (level-1) * 2 end },
level = function(level) return 22 + (level-1) end,
}
chrono_req_high5 = {
stat = { mag=function(level) return 54 + (level-1) * 2 end },
level = function(level) return 26 + (level-1) end,
}
-- Generic requires for non-spell temporal effects based on talent level
temporal_req1 = {
stat = { wil=function(level) return 12 + (level-1)*2 end},
level = function(level) return 0 + (level-1) end,
}
temporal_req2 = {
stat = { wil=function(level) return 20 + (level-1)*2 end},
level = function(level) return 4 + (level-1) end,
}
temporal_req3 = {
stat = { wil=function(level) return 28 + (level-1) * 2 end },
level = function(level) return 8 + (level-1) end,
}
temporal_req4 = {
stat = { wil=function(level) return 36 + (level-1) * 2 end },
level = function(level) return 12 + (level-1) end,
}
temporal_req5 = {
stat = { wil=function(level) return 44 + (level-1) * 2 end },
level = function(level) return 16 + (level-1) end,
}
load("/data/talents/chronomancy/age-manipulation.lua")
load("/data/talents/chronomancy/blade-threading.lua")
load("/data/talents/chronomancy/bow-threading.lua")
load("/data/talents/chronomancy/chronomancy.lua")
load("/data/talents/chronomancy/energy.lua")
load("/data/talents/chronomancy/fate-weaving.lua")
load("/data/talents/chronomancy/flux.lua")
load("/data/talents/chronomancy/gravity.lua")
load("/data/talents/chronomancy/guardian.lua")
load("/data/talents/chronomancy/matter.lua")
load("/data/talents/chronomancy/paradox.lua")
load("/data/talents/chronomancy/spacetime-folding.lua")
load("/data/talents/chronomancy/spacetime-weaving.lua")
load("/data/talents/chronomancy/speed-control.lua")
load("/data/talents/chronomancy/spellbinding.lua")
load("/data/talents/chronomancy/stasis.lua")
load("/data/talents/chronomancy/temporal-archery.lua")
load("/data/talents/chronomancy/temporal-combat.lua")
load("/data/talents/chronomancy/temporal-hounds.lua")
load("/data/talents/chronomancy/threaded-combat.lua")
load("/data/talents/chronomancy/timeline-threading.lua")
load("/data/talents/chronomancy/timetravel.lua")
-- Loads many functions and misc. talents
load("/data/talents/chronomancy/other.lua")
-- Anomalies, not learnable talents that may be cast instead of the intended spell when paradox gets to high
load("/data/talents/chronomancy/anomalies.lua")
-- Paradox Functions
-- Paradox modifier. This dictates paradox cost and spellpower scaling
-- Note that 300 is the optimal balance
-- Caps at -50% and +50%
getParadoxModifier = function (self)
local paradox = self:getParadox()
local pm = math.sqrt(paradox / 300)
if paradox < 300 then pm = paradox/300 end
pm = util.bound(pm, 0.5, 1.5)
return pm
end
-- Paradox cost (regulates the cost of paradox talents)
getParadoxCost = function (self, t, value)
local pm = getParadoxModifier(self)
local multi = 1
if self:attr("paradox_cost_multiplier") then
multi = 1 - self:attr("paradox_cost_multiplier")
end
return (value * pm) * multi
end
-- Paradox Spellpower (regulates spellpower for chronomancy)
getParadoxSpellpower = function(self, t, mod, add)
local pm = getParadoxModifier(self)
local mod = mod or 1
-- Empower?
local p = self:isTalentActive(self.T_EMPOWER)
if p and p.talent == t.id then
pm = pm + self:callTalent(self.T_EMPOWER, "getPower")
end
local spellpower = self:combatSpellpower(mod * pm, add)
return spellpower
end
-- Extension Spellbinding
getExtensionModifier = function(self, t, value)
local pm = getParadoxModifier(self)
local mod = 1
local p = self:isTalentActive(self.T_EXTENSION)
if p and p.talent == t.id then
mod = mod + self:callTalent(self.T_EXTENSION, "getPower")
end
-- paradox modifier rounds down
value = math.floor(value * pm)
-- extension modifier rounds up
value = math.ceil(value * mod)
return math.max(1, value)
end
-- Tunes paradox
tuneParadox = function(self, t, value)
local dox = self:getParadox() - (self.preferred_paradox or 300)
local fix = math.min( math.abs(dox), value )
if dox > 0 then
self:incParadox( -fix )
elseif dox < 0 then
self:incParadox( fix )
end
end
--- Warden weapon functions
-- Checks for weapons in main and quickslot
doWardenPreUse = function(self, weapon, silent)
if weapon == "bow" then
local bow, ammo, oh, pf_bow= self:hasArcheryWeapon("bow")
if not bow and not pf_bow then
bow, ammo, oh, pf_bow= self:hasArcheryWeapon("bow", true)
end
return bow or pf_bow, ammo
end
if weapon == "dual" then
local mh, oh = self:hasDualWeapon()
if not mh then
mh, oh = self:hasDualWeaponQS()
end
return mh, oh
end
end
-- Swaps weapons if needed
doWardenWeaponSwap = function(self, t, type, silent)
local swap = false
local mainhand, offhand, ammo, pf_weapon
if type == "blade" then
mainhand, offhand = self:hasDualWeapon()
if not mainhand and self:hasDualWeapon(nil, true) then -- weird but this is lets ogers offhanding daggers still swap
swap = true
end
end
if type == "bow" then
mainhand, offhand, ammo, pf_weapon = self:hasArcheryWeapon("bow")
if not mainhand and not pf_weapon then
mainhand, offhand, ammo, pf_weapon = self:hasArcheryWeapon("bow", true)
if mainhand or pf_weapon then swap = true end
end
end
if swap == true then
local old_inv_access = self.no_inventory_access -- Make sure clones can swap
self.no_inventory_access = nil
self:attr("no_sound", 1)
self:quickSwitchWeapons(true, "warden", silent)
self:attr("no_sound", -1)
self.no_inventory_access = old_inv_access
end
return swap
end
-- Target helper function for focus fire
checkWardenFocus = function(self)
local target
local eff = self:hasEffect(self.EFF_WARDEN_S_FOCUS)
if eff then
target = eff.target
end
return target
end
-- Spell functions
--- Create a temporal clone
-- @param[type=table] self Actor doing the cloning. Not currently used.
-- @param[type=table] target Actor to be cloned.
-- @param[type=int] duration How many turns the clone lasts. Zero is allowed.
-- @param[type=table] alt_nodes Optional, these nodes will use a specified key/value on the clone instead of copying from the target.
-- @ Table keys should be the nodes to skip (field name or table reference).
-- @ Each key should be set to false (to skip assignment entirely) or a table with up to two nodes:
-- @ k = a name/ref to substitute for instances of this field,
-- @ or nil to use the default name/ref as keys on the clone
-- @ v = the value to assign for instances of this node,
-- @ or nil to use the default assignent value
-- @return a reference to the clone on success, or nil on failure
makeParadoxClone = function(self, target, duration, alt_nodes)
if not target or not duration then return nil end
if duration < 0 then duration = 0 end
-- Don't copy certain properties from the target
alt_nodes = alt_nodes or {}
alt_nodes[target:getInven("INVEN")] = false -- Skip main inventory; equipped items are still copied
alt_nodes.quests = false
alt_nodes.random_escort_levels = false
alt_nodes.achievements = false
alt_nodes.achievement_data = false
alt_nodes.last_learnt_talents = false
alt_nodes.died = false
alt_nodes.died_times = false
alt_nodes.killedBy = false
alt_nodes.all_kills = false
alt_nodes.all_kills_kind = false
alt_nodes.running_fov = false
alt_nodes.running_prev = false
alt_nodes._mo = false
alt_nodes._last_mo = false
alt_nodes.add_mos = false
alt_nodes.add_displays = false
-- Don't copy some additional properties for short-lived clones
if duration == 0 then
alt_nodes.__particles = {v = {} }
alt_nodes.hotkey = false
alt_nodes.talents_auto = {v = {} }
alt_nodes.talents_confirm_use = false
end
-- Clone the target
local m = target:cloneCustom(alt_nodes)
-- Basic setup
m.dead = false
m.no_drops = true
m.keep_inven_on_death = false
m.faction = target.faction
m.summoner = target
m.summoner_gain_exp = true
m.summon_time = duration
m.ai_target = {actor = nil}
m.ai = "summoned"
m.ai_real = "tactical"
m.name = "" .. target.name .. "'s temporal clone"
m.desc = [[A creature from another timeline.]]
-- Remove some values
--m:removeAllMOs()
m.make_escort = nil
m.escort_quest = nil
m.on_added_to_level = nil
m.on_added = nil
m.game_ender = nil
mod.class.NPC.castAs(m)
engine.interface.ActorAI.init(m, m)
-- Change some values
m.exp_worth = 0
m.energy.value = 0
m.player = nil
m.max_life = m.max_life
m.life = util.bound(m.life, 0, m.max_life)
m.forceLevelup = function() end
m.on_die = nil
m.die = nil
m.puuid = nil
m.on_acquire_target = nil
m.no_inventory_access = true
m.no_levelup_access = true
m.on_takehit = nil
m.seen_by = nil
m.can_talk = nil
m.clone_on_hit = nil
m.unused_talents = 0
m.unused_generics = 0
m.unused_talents_types = 0
m.unused_prodigies = 0
if m.talents.T_SUMMON then m.talents.T_SUMMON = nil end
if m.talents.T_MULTIPLY then m.talents.T_MULTIPLY = nil end
-- Clones never flee because they're awesome
m.ai_tactic = m.ai_tactic or {}
m.ai_tactic.escape = 0
-- Remove some talents
local tids = {}
for tid, _ in pairs(m.talents) do
local t = m:getTalentFromId(tid)
if (t.no_npc_use and not t.allow_temporal_clones) or t.remove_on_clone then tids[#tids+1] = t end
end
for i, t in ipairs(tids) do
if t.mode == "sustained" and m:isTalentActive(t.id) then m:forceUseTalent(t.id, {ignore_energy=true, silent=true}) end
m:unlearnTalentFull(t.id)
end
-- Remove timed effects
m:removeTimedEffectsOnClone()
-- Reset folds for our Warden clones
for tid, cd in pairs(m.talents_cd) do
local t = m:getTalentFromId(tid)
if t.type[1]:find("^chronomancy/manifold") and m:knowTalent(tid) then
m:alterTalentCoolingdown(t, -cd)
end
end
-- A bit of sanity in case anyone decides they should blow up the world..
if m.preferred_paradox and m.preferred_paradox > 600 then m.preferred_paradox = 600 end
-- Prevent respawning
m.self_resurrect = nil
return m
end
-- Make sure we don't run concurrent chronoworlds; to prevent lag and possible game breaking bugs or exploits
checkTimeline = function(self)
if game._chronoworlds == nil then
return false
else
return true
end
end