diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index b4ca085b488d5bf467161f95bc9b79fccfa1dbd2..e4fb8ddfbb1176895433a2365c4d14cf9e953b55 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -1406,6 +1406,7 @@ function _M:move(x, y, force)
 			else
 				local speed = self:combatMovementSpeed(x, y)
 				local use_energy = true
+				if self:attr("free_movement") then use_energy = false end
 				if self:attr("walk_sun_path") then
 					for i, e in ipairs(game.level.map.effects) do if e.damtype == DamageType.SUN_PATH and e.grids[x] and e.grids[x][y] then use_energy = false break end end
 				end
@@ -6868,13 +6869,13 @@ end
 
 --- Checks if the talent can be learned
 function _M:canLearnTalent(t, ...)
-	local ret = engine.interface.ActorTalents.canLearnTalent(self, t, ...)
+	local status, reason = engine.interface.ActorTalents.canLearnTalent(self, t, ...)
 	if (t.is_class_evolution or t.is_race_evolution) and self:attr("has_evolution") then
 		if not self.is_in_uber_dialog or not self.is_in_uber_dialog.levelup_end_prodigies or not self.is_in_uber_dialog.levelup_end_prodigies[t.id] then
 			return nil, _t"can only learn one evolution"
 		end
 	end
-	return ret
+	return status, reason
 end
 
 --- Formats the requirements as a (multiline) string
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index fb78a357a2c3b0f7de25d2c9bc8da29815d040aa..2a0b14f22a949a71f817f57cefa67c005598747f 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -4384,3 +4384,25 @@ newDamageType{
 		end
 	end,
 }
+
+-- Unresistible damage, always uses highest resistance penetration
+-- NEVER add items that resist that! Use sparingly!
+newDamageType{
+	name = _t"thaumic energy", type = "THAUM", text_color = "#C259D0#",
+	death_message = {_t"utterly vaporized", _t"annihilated", _t"disintegrated"},
+	projector = function(src, x, y, type, dam, state)
+		state = initState(state)
+		useImplicitCrit(src, state)
+		if src.combatGetResistPen then
+			if not src.auto_highest_resists_pen then src.auto_highest_resists_pen = {} end
+			if not src.auto_highest_resists_pen[DamageType.THAUM] then src.auto_highest_resists_pen[DamageType.THAUM] = 1 end
+		end
+		if src.combatGetDamageIncrease then
+			if not src.auto_highest_inc_damage then src.auto_highest_inc_damage = {} end
+			if not src.auto_highest_inc_damage[DamageType.THAUM] then src.auto_highest_inc_damage[DamageType.THAUM] = 1 end
+		end
+		local target = game.level.map(x, y, Map.ACTOR)
+		if target and target:hasEffect(target.EFF_WET) then dam = dam * 1.3 end
+		return DamageType.defaultProjector(src, x, y, type, dam, state)
+	end,
+}
diff --git a/game/modules/tome/data/gfx/talents/elemental_array_burst.png b/game/modules/tome/data/gfx/talents/elemental_array_burst.png
new file mode 100644
index 0000000000000000000000000000000000000000..9c3e5e254511db2987136fe089d729b8fc892b76
Binary files /dev/null and b/game/modules/tome/data/gfx/talents/elemental_array_burst.png differ
diff --git a/game/modules/tome/data/gfx/talents/multicaster.png b/game/modules/tome/data/gfx/talents/multicaster.png
new file mode 100644
index 0000000000000000000000000000000000000000..a551e45627a79f3b8373b4ae50cfd0835952f581
Binary files /dev/null and b/game/modules/tome/data/gfx/talents/multicaster.png differ
diff --git a/game/modules/tome/data/gfx/talents/orb_of_thaumaturgy.png b/game/modules/tome/data/gfx/talents/orb_of_thaumaturgy.png
new file mode 100644
index 0000000000000000000000000000000000000000..ad8206b040b0e478cd19e8ba282d9ee8e8c3aa27
Binary files /dev/null and b/game/modules/tome/data/gfx/talents/orb_of_thaumaturgy.png differ
diff --git a/game/modules/tome/data/gfx/talents/slipstream.png b/game/modules/tome/data/gfx/talents/slipstream.png
new file mode 100644
index 0000000000000000000000000000000000000000..3fb1643dd20064ff43d4b81cf76ec212925e92d0
Binary files /dev/null and b/game/modules/tome/data/gfx/talents/slipstream.png differ
diff --git a/game/modules/tome/data/talents/spells/air.lua b/game/modules/tome/data/talents/spells/air.lua
index 1be809561dd6e9f363a744100758d0c06d96027c..cd285f23142fd3bfc018afa98a37b48feca98c8e 100644
--- a/game/modules/tome/data/talents/spells/air.lua
+++ b/game/modules/tome/data/talents/spells/air.lua
@@ -30,10 +30,11 @@ newTalent{
 	reflectable = true,
 	requires_target = true,
 	target = function(self, t)
-		if self:attr("archmage_widebeam") then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
+		if thaumaturgyCheck(self) then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
 		return {type="beam", range=self:getTalentRange(t), talent=t}
 	end,
 	allow_for_arcane_combat = true,
+	is_beam_spell = true,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 20, 350) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
@@ -43,7 +44,7 @@ newTalent{
 		self:project(tg, x, y, DamageType.LIGHTNING_DAZE, {dam=rng.avg(dam / 3, dam, 3), daze=self:attr("lightning_daze_tempest") or 0})
 		local _ _, x, y = self:canProject(tg, x, y)
 
-		if self:attr("archmage_widebeam") then
+		if thaumaturgyCheck(self) then
 			game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "lightning_beam_wide", {tx=x-self.x, ty=y-self.y}, core.shader.active() and {type="lightning"} or nil)
 		else
 			game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "lightning_beam", {tx=x-self.x, ty=y-self.y}, core.shader.active() and {type="lightning"} or nil)
diff --git a/game/modules/tome/data/talents/spells/arcane.lua b/game/modules/tome/data/talents/spells/arcane.lua
index d325fb0d391fc061a2270d461bd5d2f629b87efc..3fce7948e0ff3dc272a2f5a5b508548941def760 100644
--- a/game/modules/tome/data/talents/spells/arcane.lua
+++ b/game/modules/tome/data/talents/spells/arcane.lua
@@ -32,9 +32,10 @@ newTalent{
 	proj_speed = 20,
 	direct_hit = function(self, t) if self:getTalentLevel(t) >= 3 then return true else return false end end,
 	reflectable = true,
+	is_beam_spell = true,
 	requires_target = true,
 	target = function(self, t)
-		if self:attr("archmage_widebeam") then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
+		if thaumaturgyCheck(self) then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
 		local tg = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="bolt_arcane", trail="arcanetrail"}}
 		if self:getTalentLevel(t) >= 3 then tg.type = "beam" end
 		return tg
@@ -47,7 +48,7 @@ newTalent{
 		if tg.type == "beam" or tg.type == "widebeam" then
 			self:project(tg, x, y, DamageType.ARCANE, self:spellCrit(t.getDamage(self, t)), nil)
 			local _ _, x, y = self:canProject(tg, x, y)
-			if self:attr("archmage_widebeam") then
+			if thaumaturgyCheck(self) then
 				game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "mana_beam_wide", {tx=x-self.x, ty=y-self.y})
 			else
 				game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "mana_beam", {tx=x-self.x, ty=y-self.y})
diff --git a/game/modules/tome/data/talents/spells/earth.lua b/game/modules/tome/data/talents/spells/earth.lua
index 4919c473515788645c3e6803d88acb2693cb109f..cb788855bba6e5590a86d242d92fa259308a4d46 100644
--- a/game/modules/tome/data/talents/spells/earth.lua
+++ b/game/modules/tome/data/talents/spells/earth.lua
@@ -30,10 +30,11 @@ newTalent{
 	is_body_of_stone_affected = true,
 	range = 10,
 	tactical = { ATTACK = {PHYSICAL = 2} },
+	is_beam_spell = true,
 	direct_hit = true,
 	requires_target = true,
 	target = function(self, t)
-		if self:attr("archmage_widebeam") then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
+		if thaumaturgyCheck(self) then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
 		local tg = {type="beam", range=self:getTalentRange(t), talent=t}
 		return tg
 	end,
@@ -55,7 +56,7 @@ newTalent{
 		tg.range = self:getTalentRange(t)
 		self:project(tg, x, y, DamageType.PHYSICAL, self:spellCrit(t.getDamage(self, t)), nil)
 		local _ _, x, y = self:canProject(tg, x, y)
-		if self:attr("archmage_widebeam") then 
+		if thaumaturgyCheck(self) then 
 			game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "earth_beam_wide", {tx=x-self.x, ty=y-self.y})
 		else
 			game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "earth_beam", {tx=x-self.x, ty=y-self.y})
diff --git a/game/modules/tome/data/talents/spells/fire.lua b/game/modules/tome/data/talents/spells/fire.lua
index 14e9982e4d2b2f9a0bd4f08b018a6e9e79c37dde..785c5393aebd55d18e88ed8589da81e50223b42f 100644
--- a/game/modules/tome/data/talents/spells/fire.lua
+++ b/game/modules/tome/data/talents/spells/fire.lua
@@ -28,10 +28,11 @@ newTalent{
 	tactical = { ATTACK = { FIRE = 2 } },
 	range = 10,
 	reflectable = true,
+	is_beam_spell = true,
 	proj_speed = 20,
 	requires_target = true,
 	target = function(self, t)
-		if self:attr("archmage_widebeam") then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
+		if thaumaturgyCheck(self) then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end
 		local tg = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="bolt_fire", trail="firetrail"}}
 		if self:getTalentLevel(t) >= 5 then tg.type = "beam" end
 		return tg
@@ -45,7 +46,7 @@ newTalent{
 		if not x or not y then return nil end
 		local grids = nil
 
-		if self:attr("archmage_widebeam") then
+		if thaumaturgyCheck(self) then
 			grids = self:project(tg, x, y, DamageType.FIREBURN, self:spellCrit(t.getDamage(self, t)))
 			local _ _, x, y = self:canProject(tg, x, y)
 			game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam_wide", {tx=x-self.x, ty=y-self.y})
diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua
index e3bbf5d750159f5522e5d903cfeca874ad9d6c3a..48a1580f45e8002dfb6d0e1f0ee24dcf0f54a205 100644
--- a/game/modules/tome/data/talents/spells/spells.lua
+++ b/game/modules/tome/data/talents/spells/spells.lua
@@ -119,6 +119,15 @@ spells_req_high5 = {
 	level = function(level) return 26 + (level-1)  end,
 }
 
+function thaumaturgyCheck(self)
+	if not self:attr("archmage_widebeam") then return false end
+	local inven = self:getInven("BODY")
+	if not inven then return true end
+	if not inven[1] then return true end
+	if inven[1].type ~= "armor" or inven[1].subtype ~= "cloth" then return false end
+	return true
+end
+
 -------------------------------------------
 -- Necromancer minions
 function necroArmyStats(self)
diff --git a/game/modules/tome/data/talents/spells/thaumaturgy.lua b/game/modules/tome/data/talents/spells/thaumaturgy.lua
index 167f245dedf35ae672fd74a5c2a8e7a374ab92c4..9f8b51737ed01933838e8d583b674c3020bbb387 100644
--- a/game/modules/tome/data/talents/spells/thaumaturgy.lua
+++ b/game/modules/tome/data/talents/spells/thaumaturgy.lua
@@ -22,16 +22,27 @@ newTalent{
 	type = {"spell/thaumaturgy",1},
 	require = spells_req_high1,
 	points = 5,
-	mana = 40,
+	mana = 35,
 	cooldown = 20,
+	range = 10,
 	use_only_arcane = 5,
 	tactical = { BUFF=2 },
+	on_pre_use = thaumaturgyCheck,
+	requires_target = true,
+	no_energy = true,
+	target = function(self, t) return {type="hit", range=self:getTalentRange(t)} end,
+	getDur = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	action = function(self, t)
+		local tg = self:getTalentTarget(t)
+		local x, y = self:getTargetLimitedWallStop(tg)
+		if not x or not y then return nil end
+		self:setEffect(self.EFF_ORB_OF_THAUMATURGY, t:_getDur(self), {x=x, y=y})
 		return true
 	end,
 	info = function(self, t)
-		return ([[
-		]]):tformat()
+		return ([[You create an orb attuned to thaumaturgy for %d turns.
+		While it lasts, any beam spell you cast will be duplicated and also cast for free at the orb.
+		]]):tformat(t:_getDur(self))
 	end,
 }
 
@@ -40,164 +51,124 @@ newTalent{
 	type = {"spell/thaumaturgy",2},
 	require = spells_req_high2,
 	points = 5,
-	mana = 30,
-	cooldown = 6,
+	mode = "sustained",
+	sustain_mana = 70,
+	cooldown = 30,
 	use_only_arcane = 5,
-	tactical = { ATTACKAREA = { TEMPORAL = 2, ARCANE = 2 }, },
-	range = 10,
-	target = function(self, t) return {type="widebeam", force_max_range=true, radius=1, range=self:getTalentRange(t), selffire=false, talent=t} end,
-	getSlow = function(self, t) return math.min(self:getTalentLevel(t) * 0.05, 0.5) end,
-	getProj = function(self, t) return math.min(90, 5 + self:combatTalentSpellDamage(t, 5, 500) / 10) end,
-	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 28, 370) end,
-	on_pre_use = function(self, t, silent)
-		if not self:knowTalent(self.T_TINKER_ARCANE_DYNAMO) then if not silent then game.logPlayer(self, "You need an arcane dynamo to cast this spell.") end return false end
-		if not self:isTalentActive(self.T_METATEMPORAL_SPINNER) then if not silent then game.logPlayer(self, "You need to activate Metatemporal Spinner to cast this spell.") end return false end
-		return true
-	end,
-	callbackOnCloned = function(self, t)
-		self._reality_breach_remain = nil
-		self._reality_breach_desc = nil
-	end,
-	callbackOnChangeLevel = function(self, t, what)
-		self._reality_breach_remain = nil
-		self._reality_breach_desc = nil
-	end,
-	callbackOnAct = function(self, t)
-		if self._reality_breach_remain then
-			self._reality_breach_remain = self._reality_breach_remain - 1
-			if self._reality_breach_remain <= 0 then
-				self._reality_breach_remain = nil
-				self._reality_breach_desc = nil
-			end
+	tactical = { BUFF = 1 },
+	on_pre_use = thaumaturgyCheck,
+	getChance = function(self, t) return self:combatTalentLimit(t, 100, 10, 30) end,
+	spells_list = {
+		["spell/fire"] = 	{ "T_FLAMESHOCK", "T_FIREFLASH", "T_INFERNO", "T_BLASTWAVE"},
+		["spell/arcane"] = 	{ "T_ARCANE_VORTEX", "T_AETHER_BEAM", "T_AETHER_BREACH"},
+		["spell/lightning"] = 	{ "T_CHAIN_LIGHTNING", "T_NOVA", "T_SHOCK" },
+		["spell/water"] = 	{ "T_GLACIAL_VAPOUR", "T_TIDAL_WAVE", "T_FREEZE", "T_FROZEN_GROUND" },
+		["spell/earth"] = 	{ "T_MUDSLIDE", "T_EARTHEN_MISSILES", "T_EARTHQUAKE" },
+	},
+	callbackOnDealDamage = function(self, t, val, target, dead, death_note)
+		if not death_note then return end
+		if death_note.source_talent_mode ~= "active" and not self._orb_of_thaumaturgy_recurs then return end
+		if not death_note.source_talent or not death_note.source_talent.is_beam_spell then return end
+
+		local tids = {}
+		for kind, list in pairs(t.spells_list) do
+			for _, tid in ipairs(list) do if self:knowTalent(tid) then
+				-- print(("[%s] %s : %d"):format(kind, tid, self:getTalentCooldown(self:getTalentFromId(tid), true)))
+				tids[#tids+1] = {tid=tid, cd=self:getTalentCooldown(self:getTalentFromId(tid), true)}
+			end end
 		end
+		local choice = rng.rarityTable(tids, "cd")
+		if not choice then return end
+		table.print(self._orb_of_thaumaturgy_recurs)
+		self:forceUseTalent(choice.tid, {ignore_cooldown=true, ignore_energy=true, force_target=self._orb_of_thaumaturgy_recurs or target})
+	end,	
+	activate = function(self, t)
+		local ret = {}
+		return ret
 	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 core.fov.distance(self.x, self.y, x, y) < 1 then return nil end
-
-		local tgts = {}
-		local dam = self:spellCrit(t.getDamage(self, t))
-		local slow = t.getSlow(self, t)
-		local proj = t.getProj(self, t)
-		self:project(tg, x, y, function(px, py)
-			DamageType:get(DamageType.OCCULT).projector(self, px, py, DamageType.OCCULT, dam)
-			local target = game.level.map(px, py, Map.ACTOR)
-			if target then tgts[target] = true end
-		end)
-
-		local sorted_tgts = {}
-		for target, _ in pairs(tgts) do
-			sorted_tgts[#sorted_tgts+1] = {target=target, dist=core.fov.distance(self.x, self.y, target.x, target.y)}
-		end
-		table.sort(sorted_tgts, "dist")
-
-		-- Compute beam direction to knockback all targets in that direction even if they are on the sides of the beam
-		local angle = math.atan2(y - self.y, x - self.x)
-
-		for _, tgt in ripairs(sorted_tgts) do
-			local target = tgt.target
-			if target:canBe("slow") then
-				target:setEffect(target.EFF_CONGEAL_TIME, 4, {slow=slow, proj=proj, apply_power=self:combatSpellpower()})
-			end
-			if self:getTalentLevel(t) >= 5 and target:canBe("knockback") then
-				target:pull(target.x + math.cos(angle) * 10, target.y + math.sin(angle) * 10, 3)
-			end
-		end
-
-		if self:getTalentLevel(t) >= 3 then
-			self:projectApply(tg, x, y, Map.PROJECTILE, function(proj, px, py)
-				proj:terminate(px, py)
-				game.level:removeEntity(proj, true)
-				proj.dead = true
-				self:logCombat(proj, "#Source# annihilates '#Target#'!")
-			end)
-		end
-
-		game.level.map:particleEmitter(self.x, self.y, 10, "time_breach", {tx=x-self.x, ty=y-self.y})
-		game.level.map:particleEmitter(self.x, self.y, 10, "time_breach", {tx=x-self.x, ty=y-self.y})
-		game.level.map:particleEmitter(self.x, self.y, 10, "time_breach3", {tx=x-self.x, ty=y-self.y})
-		game.level.map:particleEmitter(self.x, self.y, 10, "time_breach2", {tx=x-self.x, ty=y-self.y})
-		if core.shader.allow("distort") then
-			game.level.map:particleEmitter(self.x, self.y, 10, "time_breach_distort", {tx=x-self.x, ty=y-self.y})
-		end
-
-		game:shakeScreen(10, 3)
-		game:playSoundNear(self, "talents/reality_breach")
-
-		local effect_desc = {
-			from = {x=self.x, y=self.y},
-			to = {x=x, y=y},
-			range = self:getTalentRange(t),
-		}
-
-		if self:hasEffect(self.EFF_METAPHASIC_ECHO) then
-			local eff = self:hasEffect(self.EFF_METAPHASIC_ECHO)
-			eff.list[#eff.list+1] = effect_desc
-		end
-
-		self._reality_breach_desc = effect_desc
-		self._reality_breach_remain = 2
-
+	deactivate = function(self, t)
 		return true
 	end,
 	info = function(self, t)
-		local damage = t.getDamage(self, t)
-		local slow = t.getSlow(self, t)
-		local proj = t.getProj(self, t)
-		return ([[Spin your saw at incredible speeds for an instant, fully breaking reality in a 3-wide beam in front of you.
-		Any creatures caught by the beam take %0.2f occult damage and are untethered from reality, reducing their global speed by %d%% and the speed of any projectiles they fire by %d%% for 4 turns.
-		At level 3 any projectiles caught in the beam are instantly annihilated.
-		At level 5 the beam is so strong that all creatures caught inside are knocked back 3 tiles.
-		The breach is so deep that the beam will always have the maximum possible length it can.
-		The damage will increase with your Spellpower.]]):tformat(damDesc(self, DamageType.OCCULT, damage), 100 * slow, proj)
+		return ([[Casting beam spells has become so instinctive for you that you can now easily weave in other spells at the same time.
+		Anytime you cast a beam spell there is a %d%% chance to automatically cast an offensive spell that you know. This can only happen once per turn.
+		Beam spells duplicated by the Orb of Thaumaturgy can also trigger this effect.
+		The additional cast will cost mana but no turn and will not active its cooldown.]]):
+		tformat(t:_getChance(self))
 	end,
 }
 
 newTalent{
-	name = "Splipstream",
+	name = "Slipstream",
 	type = {"spell/thaumaturgy",3},
 	require = spells_req_high3,
 	points = 5,
+	mode = "sustained",
 	mana = 40,
-	cooldown = 12,
+	cooldown = 20,
 	use_only_arcane = 5,
-	tactical = { ATTACKAREA = { ARCANE = 1, TEMPORAL = 1 }, },
-	radius = 10,
-	target = function(self, t) return {type="ball", radius=self:getTalentRadius(t), talent=t} end,
-	getDur = function(self, t) return self:combatTalentLimit(t, 25, 6, 15) end,
-	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 10, 220) / 10 end,
-	on_pre_use = function(self, t, silent)
-		if not self:knowTalent(self.T_TINKER_ARCANE_DYNAMO) then if not silent then game.logPlayer(self, "You need an arcane dynamo to cast this spell.") end return false end
-		if not self:isTalentActive(self.T_METATEMPORAL_SPINNER) then if not silent then game.logPlayer(self, "You need to activate Metatemporal Spinner to cast this spell.") end return false end
-		return true
+	tactical = { BUFF=1 },
+	getNb = function(self, t) return math.floor(self:combatTalentScale(t, 3, 9)) end,
+	iconOverlay = function(self, t, p)
+		local val = p.charges
+		if val <= 0 then return "" end
+		return tostring(math.ceil(val)), "buff_font_small"
 	end,
-	action = function(self, t)
-		local tg = self:getTalentTarget(t)
-		local tgts = self:projectCollect(tg, self.x, self.y, Map.ACTOR, function(target)
-			return self:reactionToward(target) < 0 and target:hasEffect(target.EFF_CONGEAL_TIME)
-		end)
-		if not next(tgts) then return nil end
+	resetStream = function(self, t, ret)
+		ret.been_used = nil
+		ret.charges = t:_getNb(self)
+	end,
+	useStream = function(self, t)
+		local p = self:isTalentActive(t.id)
+		if not p then return end
+		p.charges = p.charges - 1
+		p.been_used = true
+		if p.charges <= 0 then self:forceUseTalent(t.id, {ignore_energy=true}) end
+	end,
+	callbackOnActBase = function(self, t)
+		local p = self:isTalentActive(t.id)
+		if not p then return end
 
-		local dam = self:spellCrit(t.getDamage(self, t))
-		for target, _ in pairs(tgts) do
-			target:setEffect(target.EFF_ETHEREAL_STEAM, t.getDur(self, t), {src=self, dam=dam})
+		if p.out_combat_turn and p.out_combat_turn < game.turn - 100 and p.been_used then
+			local mana = util.getval(t.mana, self, t) or 0
+			mana = self:alterTalentCost(t, "mana", mana)
+
+			if self:getMana() < mana then
+				game.logPlayer(self, "#PURPLE#Your Slipstream does not have enough resources!")
+				self:forceUseTalent(t.id, {ignore_energy=true})
+			else
+				game.logPlayer(self, "#PURPLE#Your Slipstream regenerates to full!")
+				t:_resetStream(self, p)
+				self:incMana(-mana)
+			end
 		end
-
-		game:playSoundNear(self, "talents/arcane")
+	end,
+	callbackOnCombat = function(self, t, state)
+		local p = self:isTalentActive(t.id)
+		if not p then return end
+		if state then p.out_combat_turn = nil
+		else p.out_combat_turn = game.turn
+		end
+	end,
+	callbackOnDealDamage = function(self, t, val, target, dead, death_note)
+		if not death_note then return end
+		if death_note.source_talent_mode ~= "active" then return end
+		if not death_note.source_talent or not death_note.source_talent.is_beam_spell then return end
+		self:setEffect(self.EFF_SLIPSTREAM, 1, {})
+	end,	
+	on_pre_use = thaumaturgyCheck,
+	activate = function(self, t)
+		local ret = { charges = t:_getNb(self) }
+		return ret
+	end,
+	deactivate = function(self, t)
 		return true
 	end,
 	info = function(self, t)
-		local damage = t.getDamage(self, t)
-		local radius = self:getTalentRadius(t)
-		return ([[You reach out through the aether to all creatures in sight that were slowed by Reality Breach or Congeal Time.
-		For each target you create a link of arcane infused steam to it that lasts %d turns.
-		Any time the target uses a talent one of your cooling down spells is reduced by 1 (prioritizing Technomancy spells).
-		Each turn the link is up the target and any creature inside the link takes %0.2f occult damage.
-		As long as at least one link is up, the cooldown of your Metaphasic Spin spell is set to 6 turns instead of 30.
-		The damage will increase with your Spellpower.]]):tformat(t.getDur(self, t), damDesc(self, DamageType.OCCULT, damage))
+		return ([[By weaving arcane triggers around you feet you can use the residual energies of your beam spells for free movement.
+		Each time you cast a beam spell you can move right afterwards without spending a turn.
+		This spell has %d charges. Once all charges are spent it unsustains.
+		If you exit combat with some charges left it will after 10 turn regenerates its charges, if you have enough mana.]]):tformat(t:_getNb(self))
 	end,
 }
 
@@ -206,70 +177,74 @@ newTalent{
 	type = {"spell/thaumaturgy",4},
 	require = spells_req_high4,
 	points = 5,
-	mana = 40,
-	cooldown = function(self, t) return self:combatTalentLimit(t, 6, 30, 20) end,
+	mana = 25,
 	use_only_arcane = 5,
-	tactical = { ATTACKAREA = { ARCANE = 2, TEMPORAL = 2 }, DISABLE = function(self, t, aitarget)
-			if self:getTalentLevel(t) < 5 then return 0 end
-			local nb = 0
-			for eff_id, p in pairs(aitarget.tmp) do
-				local e = self.tempeffect_def[eff_id]
-				if e.type ~= "other" and e.status == "beneficial" then nb = nb + 1 end
-			end
-			return nb^0.5
-		end
-	},
+	cooldown = 16,
+	tactical = { ATTACKAREA = { THAUM = 3 } },
 	range = 10,
-	target = function(self, t) return {type="ball", radius=self:getTalentRange(t), talent=t} end,
-	getReduce = function(self, t) return self:combatTalentScale(t, 1, 5) end,
-	getDur = function(self, t) return self:combatTalentScale(t, 3, 6) end,
-	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.4, 1.15) end,
-	on_pre_use = function(self, t, silent)
-		if not self:knowTalent(self.T_TINKER_ARCANE_DYNAMO) then if not silent then game.logPlayer(self, "You need an arcane dynamo to cast this spell.") end return false end
-		if not self:isTalentActive(self.T_METATEMPORAL_SPINNER) then if not silent then game.logPlayer(self, "You need to activate Metatemporal Spinner to cast this spell.") end return false end
-		if not self._reality_breach_remain then if not silent then game.logPlayer(self, "You can only cast this spell on the turn after Reality Breach.") end return false end
-		return true
-	end,
-	doEcho = function(self, t, list)
-		local p = self:isTalentActive(self.T_METATEMPORAL_SPINNER)
-		if not p then return end
-		local weapon = p.o
+	is_beam_spell = true,
+	requires_target = true,
+	target = function(self, t) return {type="widebeam", force_max_range=true, radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()} end,
+	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 28, 370) end,
+	callbackOnDealDamage = function(self, t, val, target, dead, death_note)
+		if not death_note then return end
+		if death_note.source_talent_mode ~= "active" then return end
+		if not death_note.source_talent or not death_note.source_talent.is_beam_spell or death_note.source_talent.id == t.id then return end
+		if self.turn_procs.elemental_array_burst then return end
+		self.turn_procs.elemental_array_burst = true
+		self:alterTalentCoolingdown(t.id, -1)
+	end,	
+	on_pre_use = thaumaturgyCheck,
+	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 dam_tgts = {}
-		for _, data in ipairs(list) do
-			local x, y = data.to.x, data.to.y
-			self:projectCollect({type="widebeam", radius=1, force_max_range=true, range=data.range, selffire=false, x=data.from.x, y=data.from.y}, x, y, Map.ACTOR, nil, dam_tgts)
+		local dam = self:spellCrit(t.getDamage(self, t))
+		local grids = self:project(tg, x, y, DamageType.THAUM, dam)
 
-			game.level.map:particleEmitter(data.from.x, data.from.y, data.range, "time_breach", {a=0.5, tx=x-data.from.x, ty=y-data.from.y})
-			game.level.map:particleEmitter(data.from.x, data.from.y, data.range, "flying_sawblade", {size_factor=2, image="particles_images/arcane_sawblade_smaller", tx=x-data.from.x, ty=y-data.from.y})
-			if core.shader.allow("distort") then
-				game.level.map:particleEmitter(data.from.x, data.from.y, data.range, "time_breach_distort", {tx=x-data.from.x, ty=y-data.from.y})
+		if self:attr("burning_wake") and grids then
+			for px, ys in pairs(grids) do
+				for py, _ in pairs(ys) do
+					game.level.map:addEffect(self, px, py, 4, engine.DamageType.INFERNO, self:attr("burning_wake"), 0, 5, nil, {type="inferno"}, nil, self:spellFriendlyFire())
+				end
 			end
 		end
-		for target, _ in pairs(dam_tgts) do
-			self.turn_procs.auto_melee_hit = true
-			self:attackTargetWith(target, weapon.combat, DamageType.OCCULT, t.getDamage(self, t))
-			self.turn_procs.auto_melee_hit = nil
 
-			target:removeEffectsFilter(function(eff) return eff.status == "beneficial" and eff.type ~= "other" end, 1, false, false, function(_, eff_id)
-				local p = target.tmp[eff_id]
-				p.dur = p.dur - t.getReduce(self, t)
-				if p.dur <= 0 then return true end
-			end)
+		if self:isTalentActive(self.T_HURRICANE) then
+			self:projectApply(tg, x, y, Map.ACTOR, function(target) if rng.percent(25) then self:callTalent(self.T_HURRICANE, "do_hurricane", target) end end)
 		end
-		game:shakeScreen(4, 1)
-	end,
-	action = function(self, t)
-		self:setEffect(self.EFF_METAPHASIC_ECHO, t.getDur(self, t), {list={self._reality_breach_desc}})
-		game:playSoundNear(self, "talents/arcane")
+		if self:isTalentActive(self.T_CRYSTALLINE_FOCUS) then
+			self:projectApply(tg, x, y, Map.ACTOR, function(target) if rng.percent(25) then 
+				if target:canBe("stun") then target:setEffect(target.EFF_STUNNED, 3, {apply_power=self:combatSpellpower()}) end
+			end end)
+		end
+		if self:isTalentActive(self.T_UTTERCOLD) then
+			self:projectApply(tg, x, y, Map.ACTOR, function(target) if rng.percent(25) then 
+				if target:canBe("stun") then target:setEffect(target.EFF_FROZEN, 3, {apply_power=self:combatSpellpower(), hp=dam}) end
+			end end)
+		end
+
+		local _ _, x, y = self:canProject(tg, x, y)
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "flamebeam_wide", {tx=x-self.x, ty=y-self.y})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "mana_beam_wide", {tx=x-self.x, ty=y-self.y})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "ice_beam_wide", {tx=x-self.x, ty=y-self.y})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "earth_beam_wide", {tx=x-self.x, ty=y-self.y})
+		game.level.map:particleEmitter(self.x, self.y, tg.radius, "lightning_beam_wide", {tx=x-self.x, ty=y-self.y})
+		game:shakeScreen(10, 3)
+		game:playSoundNear(self, "talents/reality_breach")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Using your sheer arcane power you keep breaches in spacetime open for %d turns.
-		Each turn you they are open you project an occult clone of your saw along each breach, damaging any creature caught for %d%% occult weapon damage.
-		The saw cuts both in a physical and arcane way, reducing the duration of a random beneficial effect on each target by %d each time.
-		Each target can only be affected once per turn.
-		This spell is only usable for one turn after casting Reality Breach but any Reality Breach cast during its duration is also recorded inside.]])
-		:tformat(t.getDur(self, t), t.getDamage(self, t) * 100, t.getReduce(self, t))
+		local damage = t.getDamage(self, t)
+		return ([[Using your near-perfect knowledge of beam spells you combine them all into a powerful 3-wide beam of pure energy.
+		The beam deals %0.2f thaumic damage and always goes as far as possible.
+		Thaumic damage can never be resisted by anything but "Resistance: All" and always uses your highest resistance penetration and highest damage type bonus.
+		It can trigger Burning Wake and Hurricane.
+		It is affected by the wet status.
+		It has a 25%% chance to either stun or freeze the targets for 3 turns (if Crystalline Focus or Uttercold are active, respectively).
+		Each time you deal damage with a beam spell, the remaining cooldown is reduced by 1 (this can happen only once per turn).
+		The damage will increase with your Spellpower.]]):
+		tformat(damDesc(self, DamageType.THAUM, damage))
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/water.lua b/game/modules/tome/data/talents/spells/water.lua
index 04d74e43f4ec34b2eb68a0f1044c13992e3bf1eb..b51b844ee22f81c4b52705769c474aeb25b8111b 100644
--- a/game/modules/tome/data/talents/spells/water.lua
+++ b/game/modules/tome/data/talents/spells/water.lua
@@ -27,10 +27,11 @@ newTalent{
 	tactical = { ATTACKAREA = { COLD = 1, stun = 1 } },
 	range = 10,
 	radius = 1,
+	is_beam_spell = true,
 	proj_speed = 8,
 	requires_target = true,
 	target = function(self, t)
-		if self:attr("archmage_widebeam") then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()}
+		if thaumaturgyCheck(self) then return {type="widebeam", radius=1, range=self:getTalentRange(t), talent=t, selffire=false, friendlyfire=self:spellFriendlyFire()}
 		else return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t} end
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 18, 200) end,
@@ -39,7 +40,7 @@ newTalent{
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 
-		if self:attr("archmage_widebeam") then
+		if thaumaturgyCheck(self) then
 			self:project(tg, x, y, DamageType.ICE, {chance=25, do_wet=true, dam=self:spellCrit(t.getDamage(self, t))})
 			game.level.map:particleEmitter(self.x, self.y, tg.radius, "ice_beam_wide", {tx=x-self.x, ty=y-self.y})
 		else
diff --git a/game/modules/tome/data/talents/uber/mag.lua b/game/modules/tome/data/talents/uber/mag.lua
index d072c624f4055cb3b3dfd225f99d67c86c822ff3..3d45b1570cf74064b02c9c7d6a055d94a1c42fa3 100644
--- a/game/modules/tome/data/talents/uber/mag.lua
+++ b/game/modules/tome/data/talents/uber/mag.lua
@@ -478,7 +478,8 @@ uberTalent{
 		- Orb of Thaumaturgy: a temporary orb that duplicates any beam spells that you cast
 		- Multicaster: When casting a beam spell adds a chance to also cast an other archmage spell
 		- Slipstream: Allows movement when casting beams
-		- Elemental Array Burst: a powerful, multi-elemental beam spell that can inflict all elemental ailments and can not be resisted]])
+		- Elemental Array Burst: a powerful, multi-elemental beam spell that can inflict all elemental ailments and can not be resisted
+		#CRIMSON#The fine spellcasting required for wide beams and all thaumaturgy spells can only happen while wearing cloth. Anything heavier will hinder the casting too much.]])
 		:tformat()
 	end,
 }
diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua
index 1925a19b4865ca7a84cc1ec988274c4753d09318..5e1ac1ab6748a7d6489f19b94689fae5caaa952f 100644
--- a/game/modules/tome/data/timed_effects/magical.lua
+++ b/game/modules/tome/data/timed_effects/magical.lua
@@ -5301,3 +5301,58 @@ newEffect{
 		self:unlearnTalent(self.T_GHOST_WALK_RETURN, 1, nil, {no_unlearn=true})
 	end,
 }
+
+newEffect{
+	name = "SLIPSTREAM", image = "talents/slipstream.png",
+	desc = _t"Slipstream Free Movement",
+	long_desc = function(self, eff) return _t"Can move once for free, this turn only." end,
+	type = "magical",
+	subtype = { thaumaturgy=true, movement=true },
+	status = "beneficial", decrease = 0,
+	parameters = {},
+	activate = function(self, eff)
+		self:effectTemporaryValue(eff, "free_movement", 1)
+		eff.setup = true
+	end,
+	callbackOnMove = function(self, eff, moved, force, ox, oy)
+		if not moved or force then return end
+		self:removeEffect(self.EFF_SLIPSTREAM, true, true)
+		self:callTalent(self.T_SLIPSTREAM, "useStream")
+	end,
+	callbackOnAct = function(self, eff)
+		if eff.setup then eff.setup = nil return end
+		self:removeEffect(self.EFF_SLIPSTREAM, true, true)
+	end,
+}
+
+newEffect{
+	name = "ORB_OF_THAUMATURGY", image = "talents/orb_of_thaumaturgy.png",
+	desc = _t"Orb Of Thaumaturgy",
+	long_desc = function(self, eff) return _t"All beam spells are duplicated to the orb." end,
+	type = "magical",
+	subtype = { thaumaturgy=true, meta=true },
+	status = "beneficial",
+	parameters = {},
+	callbackOnChangeLevel = function(self, eff, what)
+		if what ~= "leave" then return end
+		self:removeEffect(self.EFF_ORB_OF_THAUMATURGY, true, true)
+	end,
+	callbackOnTalentPost = function(self, eff, t)
+		if self._orb_of_thaumaturgy_recurs then return end
+		if not t.is_beam_spell then return end
+		game:onTickEnd(function()
+		local target = {x=eff.x, y=eff.y, __no_self=true}
+		self._orb_of_thaumaturgy_recurs = target
+		print("==============+HERE!!!!")
+		self:forceUseTalent(t.id, {ignore_cooldown=true, ignore_ressources=true, ignore_energy=true, force_target=target})
+		print("==============+DONE!!!!")
+		self._orb_of_thaumaturgy_recurs = nil
+		end)
+	end,
+	activate = function(self, eff)
+		eff.particle = game.level.map:particleEmitter(eff.x, eff.y, 1, "corpselight", {})
+	end,
+	deactivate = function(self, eff, ed)
+		game.level.map:removeParticleEmitter(eff.particle)
+	end,
+}
diff --git a/game/modules/tome/dialogs/LevelupDialog.lua b/game/modules/tome/dialogs/LevelupDialog.lua
index 594db1590a0062a2f6bd0e2f265c1e35f175eee7..74b3fb3f93d546044bf8ae21f9deb3f218a3e834 100644
--- a/game/modules/tome/dialogs/LevelupDialog.lua
+++ b/game/modules/tome/dialogs/LevelupDialog.lua
@@ -324,7 +324,7 @@ function _M:checkDeps(simple, ignore_special)
 
 		local t = self.actor:getTalentFromId(t_id)
 		local ok, reason = self.actor:canLearnTalent(t, 0, ignore_special)
-		if not ok and (self.actor:knowTalent(t) or force) then talents = talents.."\n#GOLD##{bold}#    - "..t.name.."#{normal}##LAST#("..reason..")" end
+		if not ok and (self.actor:knowTalent(t) or force) then talents = talents.."\n#GOLD##{bold}#    - "..t.name.."#{normal}##LAST#("..(reason or _t"unknown")..")" end
 		if reason == _t"not enough stat" then
 			stats_ok = false
 		end
diff --git a/game/modules/tome/dialogs/UseItemDialog.lua b/game/modules/tome/dialogs/UseItemDialog.lua
index 5db5105e7877ee906afd1f7b2e5c1a447c9b906a..a0821acb076ef900e2536242687478ca87b2a8f0 100644
--- a/game/modules/tome/dialogs/UseItemDialog.lua
+++ b/game/modules/tome/dialogs/UseItemDialog.lua
@@ -103,7 +103,11 @@ function _M:use(item)
 		local list = {}
 		for inven_idx, inven in pairs(self.actor.inven) do if inven.worn then
 			for item_idx, o in ipairs(inven) do
-				if o:canAttachTinker(self.object, true) then list[#list+1] = {name=o:getName{do_color=true}, inven=inven, item=item_idx, o=o} end
+				if self.actor.tinker_restrict_slots and (not self.actor.tinker_restrict_slots[inven.name] or self.actor.tinker_restrict_slots[inven.name] < item_idx) then
+					-- nothing
+				else
+					if o:canAttachTinker(self.object, true) then list[#list+1] = {name=o:getName{do_color=true}, inven=inven, item=item_idx, o=o} end
+				end
 			end
 		end end
 		local doit = function(w)