diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index b55bc09cbcc04c122246924bbb9c8672c58c7c23..dab13ac7944ffd6172b393161dcfda0a326c00f8 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -1073,7 +1073,6 @@ function _M:combatDamageRange(weapon)
 	return (self.combat_damrange or 0) + (weapon.damrange or 1.1)
 end
 
-
 --- Scale damage values
 -- This currently beefs up high-end damage values to make up for the combat stat rescale nerf.
 function _M:rescaleDamage(dam)
@@ -1081,6 +1080,7 @@ function _M:rescaleDamage(dam)
 --	return dam * (1 - math.log10(dam * 2) / 7) --this is the old version, pre-combat-stat-rescale
 	return dam ^ 1.04
 end
+
 --Diminishing-returns method of scaling combat stats, observing this rule: the first twenty ranks cost 1 point each, the second twenty cost two each, and so on. This is much, much better for players than some logarithmic mess, since they always know exactly what's going on, and there are nice breakpoints to strive for.
 function _M:rescaleCombatStats(raw_combat_stat_value)
 	local x = raw_combat_stat_value
@@ -1094,6 +1094,158 @@ function _M:rescaleCombatStats(raw_combat_stat_value)
 	return total
 end
 
+-- Scale a value up or down by a power
+-- x = a numeric value
+-- y_low = value to match at x_low
+-- y_high = value to match at x_high
+-- power = scaling factor (default 0.5)
+function _M:combatScale(x, y_low, x_low, y_high, x_high, power)
+	power = power or 0.5
+	local x_low_adj, x_high_adj = x_low^power, x_high^power
+	local m = (y_high - y_low)/(x_high_adj - x_low_adj)
+	local b = y_low - m*x_low_adj
+	return m * x^power + b
+--	return m * x^power + b, m, b
+end
+
+-- Scale a value up or down subject to a limit
+-- x = a numeric value
+-- limit = value approached as x increases
+-- y_high = value to match at when x = x_high
+-- y_low (optional) = value to match when x = x_low
+--	returns (limit - add)*x/(x + halfpoint) + add (= add when x = 0 and limit when x = infinity), halfpoint, add
+-- halfpoint and add are internally computed to match the desired high/low values
+-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
+function _M:combatLimit(x, limit, y_low, x_low, y_high, x_high)
+--	local x_low, x_high = 1,5 -- Implied talent levels for low and high values respectively
+--	local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
+	if y_low and x_low then
+		local p = limit*(x_high-x_low)
+		local m = x_high*y_high - x_low*y_low
+		local halfpoint = (p-m)/(y_high - y_low)
+		local add = (limit*(x_high*y_low-x_low*y_high) + y_high*y_low*(x_low-x_high))/(p-m)
+		return (limit-add)*x/(x + halfpoint) + add
+--		return (limit-add)*x/(x + halfpoint) + add, halfpoint, add
+	else
+		local add = 0
+		local halfpoint = limit*x_high/(y_high-add)-x_high
+		return (limit-add)*x/(x + halfpoint) + add
+--		return (limit-add)*x/(x + halfpoint) + add, halfpoint, add
+	end
+end
+
+-- Compute a diminishing returns value based on talent level that scales with a power
+-- t = talent def table or a numeric value
+-- low = value to match at talent level 1
+-- high = value to match at talent level 5
+-- power = scaling factor (default 0.5) or "log" for log10
+-- add = amount to add the result (default 0)
+-- shift = amount to add to the talent level before computation (default 0)
+-- raw if true specifies use of raw talent level
+function _M:combatTalentScale(t, low, high, power, add, shift, raw)
+	local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
+	power, add, shift = power or 0.5, add or 0, shift or 0
+	local x_low, x_high = 1, 5 -- Implied talent levels to fit
+	local x_low_adj, x_high_adj
+	if power == "log" then
+		x_low_adj, x_high_adj = math.log10(x_low+shift), math.log10(x_high+shift)
+		tl = math.max(1, tl)
+	else
+		x_low_adj, x_high_adj = (x_low+shift)^power, (x_high+shift)^power
+	end
+	local m = (high - low)/(x_high_adj - x_low_adj)
+	local b = low - m*x_low_adj
+	if power == "log" then -- always >= 0
+		return math.max(0, m * math.log10(tl + shift) + b + add)
+--		return math.max(0, m * math.log10(tl + shift) + b + add), m, b
+	else 
+		return math.max(0, m * (tl + shift)^power + b + add)
+--		return math.max(0, m * (tl + shift)^power + b + add), m, b
+	end
+end
+
+-- Compute a diminishing returns value based on a stat value that scales with a power
+-- stat == "str", "con",.... or a numeric value
+-- low = value to match when stat = 10
+-- high = value to match when stat = 100
+-- power = scaling factor (default 0.5) or "log" for log10
+-- add = amount to add the result (default 0)
+-- shift = amount to add to the stat value before computation (default 0)
+function _M:combatStatScale(stat, low, high, power, add, shift)
+	stat = type(stat) == "string" and self:getStat(stat,nil,true) or stat
+	power, add, shift = power or 0.5, add or 0, shift or 0
+	local x_low, x_high = 10, 100 -- Implied stat values to match
+	local x_low_adj, x_high_adj
+	if power == "log" then
+		x_low_adj, x_high_adj = math.log10(x_low+shift), math.log10(x_high+shift)
+		stat = math.max(1, stat)
+	else
+		x_low_adj, x_high_adj = (x_low+shift)^power, (x_high+shift)^power
+	end
+	local m = (high - low)/(x_high_adj - x_low_adj)
+	local b = low -m*x_low_adj
+	if power == "log" then -- always >= 0
+		return math.max(0, m * math.log10(stat + shift) + b + add)
+--		return math.max(0, m * math.log10(stat + shift) + b + add), m, b
+	else 
+		return math.max(0, m * (stat + shift)^power + b + add)
+--		return math.max(0, m * (stat + shift)^power + b + add), m, b
+	end
+end
+
+-- Compute a diminishing returns value based on talent level that cannot go beyond a limit
+-- t = talent def table or a numeric value
+-- limit = value approached as talent levels increase
+-- high = value at talent level 5
+-- low = value at talent level 1 (optional)
+-- raw if true specifies use of raw talent level
+--	returns (limit - add)*TL/(TL + halfpoint) + add == add when TL = 0 and limit when TL = infinity
+-- TL = talent level, halfpoint and add are internally computed to match the desired high/low values
+-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
+function _M:combatTalentLimit(t, limit, low, high, raw)
+	local x_low, x_high = 1,5 -- Implied talent levels for low and high values respectively
+	local tl = type(t) == "table" and (raw and self:getTalentLevelRaw(t) or self:getTalentLevel(t)) or t
+	if low then
+		local p = limit*(x_high-x_low)
+		local m = x_high*high - x_low*low
+		local halfpoint = (p-m)/(high - low)
+		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
+		return (limit-add)*tl/(tl + halfpoint) + add
+--		return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
+	else
+		local add = 0
+		local halfpoint = limit*x_high/(high-add)-x_high
+		return (limit-add)*tl/(tl + halfpoint) + add
+--		return (limit-add)*tl/(tl + halfpoint) + add, halfpoint, add
+	end
+end
+
+-- Compute a diminishing returns value based on a stat value that cannot go beyond a limit
+-- stat == "str", "con",.... or a numeric value
+-- limit = value approached as talent levels increase
+-- high = value to match when stat = 100
+-- low = value to match when stat = 10 (optional)
+--	returns (limit - add)*stat/(stat + halfpoint) + add == add when STAT = 0 and limit when stat = infinity
+-- halfpoint and add are internally computed to match the desired high/low values
+-- note that the progression low->high->limit must be monotone, consistently increasing or decreasing
+function _M:combatStatLimit(stat, limit, low, high)
+	local x_low, x_high = 10,100 -- Implied talent levels for low and high values respectively
+	stat = type(stat) == "string" and self:getStat(stat,nil,true) or stat
+	if low then
+		local p = limit*(x_high-x_low)
+		local m = x_high*high - x_low*low
+		local halfpoint = (p-m)/(high - low)
+		local add = (limit*(x_high*low-x_low*high) + high*low*(x_low-x_high))/(p-m)
+		return (limit-add)*stat/(stat + halfpoint) + add
+--		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
+	else
+		local add = 0
+		local halfpoint = limit*x_high/(high-add)-x_high
+		return (limit-add)*stat/(stat + halfpoint) + add
+--		return (limit-add)*stat/(stat + halfpoint) + add, halfpoint, add
+	end
+end
+
 --- Gets the damage
 function _M:combatDamage(weapon)
 	weapon = weapon or self.combat or {}
diff --git a/game/modules/tome/data/talents/spells/advanced-golemancy.lua b/game/modules/tome/data/talents/spells/advanced-golemancy.lua
index ded7c4e0c1a9f942008107ce22494535ab21f6cc..809648cda3cd4936f6251342915ba09222a363f6 100644
--- a/game/modules/tome/data/talents/spells/advanced-golemancy.lua
+++ b/game/modules/tome/data/talents/spells/advanced-golemancy.lua
@@ -74,7 +74,7 @@ newTalent{
 	require = spells_req_high3,
 	points = 5,
 	mana = 20,
-	cooldown = function(self, t) return math.floor(25 - self:getTalentLevel(t)) end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 24, 20)) end, -- Limit to > 0
 	tactical = { DEFEND = 1, ATTACK=1 },
 	getPower = function(self, t) return (60 + self:combatTalentSpellDamage(t, 15, 450)) / 7, 7, 20 + self:getTalentLevel(t) * 7 end,
 	action = function(self, t)
diff --git a/game/modules/tome/data/talents/spells/advanced-necrotic-minions.lua b/game/modules/tome/data/talents/spells/advanced-necrotic-minions.lua
index 4e1b379673cc43a6bc49e053a25e979f0ae3fb45..ae0c0d15c835e344a0666ce3e7e614f84c658bd5 100644
--- a/game/modules/tome/data/talents/spells/advanced-necrotic-minions.lua
+++ b/game/modules/tome/data/talents/spells/advanced-necrotic-minions.lua
@@ -179,11 +179,11 @@ newTalent{
 	mana = 30,
 	cooldown = 10,
 	tactical = { ATTACKAREA = { BLIGHT = 2 } },
-	radius = function(self, t) return 1 + math.floor(self:getTalentLevel(t) / 3) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 1.3, 2.7)) end,
 	range = 8,
 	requires_target = true,
 	no_npc_use = true,
-	getDamage = function(self, t) return self:combatTalentSpellDamage(t,  20, 70) end,
+	getDamage = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 20, 70), 90, 0, 0, 25, 25) end, -- Limit to 50% life
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t, first_target="friend"}
 		local tx, ty, target = self:getTarget(tg)
@@ -200,9 +200,9 @@ newTalent{
 	end,
 	info = function(self, t)
 		return ([[Minions are only tools. You may dispose of them... violently.
-		Makes the targeted minion explode for %d%% of its maximum life as blight damage.
+		Makes the targeted minion explode for %d%% of its maximum life in a radius of %d as blight damage.
 		Beware! Don't get caught in the blast!]]):
-		format(t.getDamage(self, t))
+		format(t.getDamage(self, t),t.radius(self,t))
 	end,
 }
 
@@ -235,12 +235,7 @@ newTalent{
 		if nb < 3 then return false end
 		return true
 	end,
-	getLevel = function(self, t)
-		local raw = self:getTalentLevelRaw(t)
-		if raw <= 0 then return -8 end
-		if raw > 8 then return 8 end
-		return ({-6, -4, -2, 0, 2, 4, 6, 8})[raw]
-	end,
+	getLevel = function(self, t) return math.floor(self:combatScale(self:getTalentLevel(t), -6, 0.9, 2, 5)) end, -- -6 @ 1, +2 @ 5, +5 @ 8
 	action = function(self, t)
 		local list = {}
 		if game.party and game.party:hasMember(self) then
@@ -311,7 +306,7 @@ newTalent{
 		return false
 	end,
 	getTurns = function(self, t) return math.floor(4 + self:combatTalentSpellDamage(t, 8, 20)) end,
-	getPower = function(self, t) return 25 - math.ceil(1 + self:getTalentLevel(t) * 1.5) end,
+	getPower = function(self, t) return math.floor(self:combatTalentLimit(t, 10, 22.5, 16.5)) end, -- Limit >10%
 	action = function(self, t)
 		local list = {}
 		if game.party and game.party:hasMember(self) then
@@ -347,14 +342,7 @@ newTalent{
 	points = 5,
 	mode = "passive",
 	info = function(self, t)
-		local c = getAdvancedMinionChances(self)
-		return ([[Each minion you summon has a chance to be a more advanced form of undead:
-		Vampire: %d%%
-		Master Vampire: %d%%
-		Grave Wight: %d%%
-		Barrow Wight: %d%%
-		Dread: %d%%
-		Lich: %d%%]]):
-		format(c.vampire, c.m_vampire, c.g_wight, c.b_wight, c.dread, c.lich)
+		return ([[Each minion you summon has a chance to be a more advanced form of undead. Your chance for each type of minion is as follows:%s]]):
+		format(self:callTalent(self.T_CREATE_MINIONS,"MinionChancesDesc"))
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/aegis.lua b/game/modules/tome/data/talents/spells/aegis.lua
index a58acd4ff4b49ca6d4a8d3cc7fee3a8424dbc740..f5577f705d281ac79dac6493031e2ec9946c1f13 100644
--- a/game/modules/tome/data/talents/spells/aegis.lua
+++ b/game/modules/tome/data/talents/spells/aegis.lua
@@ -92,7 +92,7 @@ newTalent{
 	use_only_arcane = 2,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	getShield = function(self, t) return 20 + self:combatTalentSpellDamage(t, 5, 500) / 10 end,
+	getShield = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 5, 500), 100, 20, 0, 55.4, 354) end,	 -- Limit < 100%
 	activate = function(self, t)
 		local shield = t.getShield(self, t)
 		game:playSoundNear(self, "talents/arcane")
@@ -126,6 +126,7 @@ newTalent{
 	no_energy = true,
 	tactical = { HEAL = 2 },
 	getShield = function(self, t) return 40 + self:combatTalentSpellDamage(t, 5, 500) / 10 end,
+	getNumEffects = function(self, t) return math.max(1,math.floor(self:combatTalentScale(t, 3, 7, "log"))) end,
 	on_pre_use = function(self, t)
 		for eff_id, p in pairs(self.tmp) do
 			local e = self.tempeffect_def[eff_id]
@@ -146,15 +147,14 @@ newTalent{
 			end
 		end
 
-		for i = 1, self:getTalentLevelRaw(t) do
+		for i = 1, t.getNumEffects(self, t) do
 			if #effs == 0 then break end
 			local eff = rng.tableRemove(effs)
-
 			eff.e.on_aegis(self, eff.p, shield)
 		end
 
 		if self:isTalentActive(self.T_DISRUPTION_SHIELD) then
-			self:setEffect(self.EFF_MANA_OVERFLOW, math.ceil(2 + self:getTalentLevel(t)), {power=shield})
+			self:setEffect(self.EFF_MANA_OVERFLOW, math.ceil(self:combatTalentScale(t, 3, 7)), {power=shield})
 		end
 
 		game:playSoundNear(self, "talents/heal")
@@ -166,6 +166,6 @@ newTalent{
 		It will affect at most %d shield effects.
 		Affected shields are: Damage Shield, Time Shield, Displacement Shield, and Disruption Shield.
 		The charging will increase with your Spellpower.]]):
-		format(shield, self:getTalentLevelRaw(t))
+		format(shield, t.getNumEffects(self, t))
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/aether.lua b/game/modules/tome/data/talents/spells/aether.lua
index 68164c1e4181b9d91a4f840e8f703bd712265d75..5a1ab6f365006a14fcf77ab08804b21f1224c15a 100644
--- a/game/modules/tome/data/talents/spells/aether.lua
+++ b/game/modules/tome/data/talents/spells/aether.lua
@@ -142,7 +142,7 @@ newTalent{
 		local tg = {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t}
 		return tg
 	end,
-	getNb = function(self, t) return 3 + math.floor(self:getTalentLevel(t) / 3) end,
+	getNb = function(self, t) return math.floor(self:combatTalentScale(t, 3.3, 4.7)) end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 20, 180) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
@@ -171,14 +171,17 @@ newTalent{
 	require = spells_req_high3,
 	points = 5,
 	mana = 60,
-	cooldown = function(self, t) local rcd = math.floor(40 - self:getTalentLevel(t) * 3) return self:attr("arcane_cooldown_divide") and rcd * self.arcane_cooldown_divide or rcd end,
+	cooldown = function(self, t)
+		local rcd = math.ceil(self:combatTalentLimit(t, 15, 37, 25)) -- Limit > 15
+		return self:attr("arcane_cooldown_divide") and rcd * self.arcane_cooldown_divide or rcd 
+	end,
 	range = 10,
 	direct_hit = true,
 	use_only_arcane = 1,
 	requires_target = true,
 	no_energy = true,
 	tactical = { BUFF = 2 },
-	getNb = function(self, t) return 4 + math.floor(self:getTalentLevel(t)) end,
+	getNb = function(self, t) return math.floor(self:combatTalentLimit(t, 15, 5, 9)) end, -- Limit duration < 15	
 	action = function(self, t)
 		self:setEffect(self.EFF_AETHER_AVATAR, t.getNb(self, t), {})
 		game:playSoundNear(self, "talents/arcane")
@@ -202,7 +205,7 @@ newTalent{
 	use_only_arcane = 1,
 	tactical = { BUFF = 2 },
 	getDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50, true) end, -- Limit < 100%	
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/arcane")
 
diff --git a/game/modules/tome/data/talents/spells/air.lua b/game/modules/tome/data/talents/spells/air.lua
index dc94d2627ff2f24c38119462641cd557d0bfd0b6..08182eb01955675a0b02fa0aa90907b3b253faa1 100644
--- a/game/modules/tome/data/talents/spells/air.lua
+++ b/game/modules/tome/data/talents/spells/air.lua
@@ -151,7 +151,7 @@ newTalent{
 	tactical = { BUFF = 2 },
 	getEncumberance = function(self, t) return math.floor(self:combatTalentSpellDamage(t, 10, 110)) end,
 	getRangedDefence = function(self, t) return self:combatTalentSpellDamage(t, 4, 30) end,
-	getSpeed = function(self, t) return 0.05 * self:getTalentLevel(t) end,
+	getSpeed = function(self, t) return self:combatTalentScale(t, 0.05, 0.25, 0.75) end,
 	getFatigue = function(self, t) return math.floor(2.5 * self:getTalentLevel(t)) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/spell_generic2")
diff --git a/game/modules/tome/data/talents/spells/arcane.lua b/game/modules/tome/data/talents/spells/arcane.lua
index a3c76718dff9d6d6cc0349f8d8f9c367f6a9a6a3..53639d49b4e4f17f47a11b9453ea941216873462 100644
--- a/game/modules/tome/data/talents/spells/arcane.lua
+++ b/game/modules/tome/data/talents/spells/arcane.lua
@@ -26,12 +26,8 @@ newTalent{
 	points = 5,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	spellpower_increase = { 5, 9, 14, 17, 20 },
 	use_only_arcane = 1,
-	getSpellpowerIncrease = function(self, t)
-		local v = t.spellpower_increase[self:getTalentLevelRaw(t)]
-		if v then return v else return 20 + (self:getTalentLevelRaw(t) - 5) * 2 end
-	end,
+	getSpellpowerIncrease = function(self, t) return self:combatTalentScale(t, 5, 20, 0.75) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/arcane")
 		return {
@@ -143,10 +139,18 @@ newTalent{
 	tactical = { MANA = 3, DEFEND = 2, },
 	getManaRatio = function(self, t) return math.max(3 - self:combatTalentSpellDamage(t, 10, 200) / 100, 0.5) * (100 - util.bound(self:attr("shield_factor") or 0, 0, 70)) / 100 end,
 	getArcaneResist = function(self, t) return 50 + self:combatTalentSpellDamage(t, 10, 500) / 10 end,
+	-- Note: effects handled in mod.class.Actor:onTakeHit function
+	getMaxDamage = function(self, t) -- Compute damage limit
+		local max_dam = self.max_mana
+		for i, k in pairs(self.sustain_talents) do -- Add up sustain costs to get total mana pool size
+			max_dam = max_dam + (tonumber(self.talents_def[i].sustain_mana) or 0)
+		end
+		return max_dam * 2 -- Maximum damage is 2x total mana pool
+	end,
 	on_pre_use = function(self, t) return (self:getMana() / self:getMaxMana() <= 0.25) or self:hasEffect(self.EFF_AETHER_AVATAR) or self:attr("disruption_shield") end,
 	explode = function(self, t, dam)
 		game.logSeen(self, "#VIOLET#%s's disruption shield collapses and then explodes in a powerful manastorm!", self.name:capitalize())
-
+		dam = math.min(dam, t.getMaxDamage(self, t)) -- Damage cap
 		-- Add a lasting map effect
 		self:setEffect(self.EFF_ARCANE_STORM, 10, {power=t.getArcaneResist(self, t)})
 		game.level.map:addEffect(self,
@@ -193,10 +197,10 @@ newTalent{
 	info = function(self, t)
 		return ([[Surround yourself with arcane forces, disrupting any attemps to harm you and instead generating mana.
 		Generates %0.2f mana per damage point taken (Aegis Shielding talent affects the ratio).
-		If your mana is brought too high by the shield, it will de-activate and the chain reaction will release a deadly arcane storm around you with radius 3 for 10 turns, dealing 10%% of the damage absorbed over the sustain's duration each turn.
+		If your mana is brought too high by the shield, it will de-activate and the chain reaction will release a deadly arcane storm around you with radius 3 for 10 turns, dealing 10%% of the damage absorbed over the sustain's duration each turn, up to a maximum of %d total damage.
 		While the arcane storm rages, you also get %d%% arcane resistance.
 		Only usable when below 25%% mana.
 		The damage to mana ratio increases with your Spellpower.]]):
-		format(t.getManaRatio(self, t), t.getArcaneResist(self, t))
+		format(t.getManaRatio(self, t), t.getMaxDamage(self, t), t.getArcaneResist(self, t))
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/conveyance.lua b/game/modules/tome/data/talents/spells/conveyance.lua
index ab50acaa4d26de4cec76f64e6e4f76acce0b0a76..0867ca1ed800cf22c9302c39dd7afbae2ea9203b 100644
--- a/game/modules/tome/data/talents/spells/conveyance.lua
+++ b/game/modules/tome/data/talents/spells/conveyance.lua
@@ -27,8 +27,8 @@ newTalent{
 	cooldown = function(self, t) return game.zone and game.zone.force_controlled_teleport and 3 or 8 end,
 	tactical = { ESCAPE = 2 },
 	requires_target = function(self, t) return self:getTalentLevel(t) >= 4 end,
-	getRange = function(self, t) return 4 + self:combatTalentSpellDamage(t, 10, 15) end,
-	getRadius = function(self, t) return math.max(0, 7 - self:getTalentLevelRaw(t)) end,
+	getRange = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 15), 40, 4, 0, 13.4, 9.4) end, -- Limit to range 40
+	getRadius = function(self, t) return math.floor(self:combatTalentLimit(t, 0, 6, 2)) end, -- Limit to radius 0	
 	is_teleport = true,
 	action = function(self, t)
 		local target = self
@@ -107,7 +107,7 @@ newTalent{
 	tactical = { ESCAPE = 3 },
 	requires_target = function(self, t) return self:getTalentLevel(t) >= 4 end,
 	getRange = function(self, t) return 100 + self:combatSpellpower(1) end,
-	getRadius = function(self, t) return 20 - self:getTalentLevel(t) end,
+	getRadius = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 19, 15)) end, -- Limit > 0
 	is_teleport = true,
 	action = function(self, t)
 		local target = self
@@ -221,7 +221,7 @@ newTalent{
 	cooldown = 40,
 	sustain_mana = 200,
 	tactical = { ESCAPE = 1, CLOSEIN = 1 },
-	getRange = function(self, t) return math.floor(4 + self:combatSpellpower(0.06) * self:getTalentLevel(t)) end,
+	getRange = function(self, t) return math.floor(self:combatScale(self:combatSpellpower(0.06) * self:getTalentLevel(t), 4, 0, 20, 16)) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/teleport")
 		return {
diff --git a/game/modules/tome/data/talents/spells/divination.lua b/game/modules/tome/data/talents/spells/divination.lua
index ed360810a26d5b64bc39cb154467cf9b17c59c14..09c2f2880e7c286ec5e5d1904e318692f81899e5 100644
--- a/game/modules/tome/data/talents/spells/divination.lua
+++ b/game/modules/tome/data/talents/spells/divination.lua
@@ -28,7 +28,7 @@ newTalent{
 	no_npc_use = true,
 	requires_target = true,
 	getDuration = function(self, t) return math.floor(10 + self:getTalentLevel(t) * 3) end,
-	getRadius = function(self, t) return math.floor(4 + self:getTalentLevel(t)) end,
+	getRadius = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
 	action = function(self, t)
 		local tg = {type="hit", nolock=true, pass_terrain=true, nowarning=true, range=100, requires_knowledge=false}
 		x, y = self:getTarget(tg)
diff --git a/game/modules/tome/data/talents/spells/earth.lua b/game/modules/tome/data/talents/spells/earth.lua
index a3156ab753ae07431d0c7b022c39bc3028381e83..b5734d615d66dfaa88a9d648a6e2d7cd4e21e555 100644
--- a/game/modules/tome/data/talents/spells/earth.lua
+++ b/game/modules/tome/data/talents/spells/earth.lua
@@ -56,11 +56,11 @@ newTalent{
 	points = 5,
 	mana = 15,
 	cooldown = 6,
-	range = function(self, t) return math.min(10, math.ceil(2 + self:getTalentLevel(t))) end,
+	range = function(self, t) return math.min(10, math.floor(self:combatTalentScale(t, 3, 7))) end,
 	tactical = { ATTACK = {PHYSICAL = 2} },
 	direct_hit = true,
 	requires_target = true,
-	getDigs = function(self, t) return self:getTalentLevelRaw(t) end,
+	getDigs = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 30, 300) end,
 	target = function(self, t)
 		local tg = {type="beam", range=self:getTalentRange(t), talent=t}
@@ -100,7 +100,7 @@ newTalent{
 	direct_hit = true,
 	tactical = { ATTACKAREA = { PHYSICAL = 2 }, DISABLE = { knockback = 2 }, ESCAPE = { knockback = 1 } },
 	range = 0,
-	radius = function(self, t) return 3 + self:getTalentLevelRaw(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	requires_target = true,
 	target = function(self, t) return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t} end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 10, 250) end,
diff --git a/game/modules/tome/data/talents/spells/enhancement.lua b/game/modules/tome/data/talents/spells/enhancement.lua
index b121d054cc8fa954a14b8ff4bd8ea3d5c4497281..85cda0cb2b5e18482f66eb59e3506adb5e807f6b 100644
--- a/game/modules/tome/data/talents/spells/enhancement.lua
+++ b/game/modules/tome/data/talents/spells/enhancement.lua
@@ -120,7 +120,7 @@ newTalent{
 	cooldown = 10,
 	sustain_mana = 75,
 	tactical = { BUFF = 2 },
-	getStatIncrease = function(self, t) return math.min(math.floor(self:combatTalentSpellDamage(t, 2, 10)), 11) end,
+	getStatIncrease = function(self, t) return math.floor(self:combatTalentSpellDamage(t, 2, 10)) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/spell_generic")
 		local power = t.getStatIncrease(self, t)
@@ -141,7 +141,7 @@ newTalent{
 	end,
 	info = function(self, t)
 		local statinc = t.getStatIncrease(self, t)
-		return ([[You concentrate on your inner self, increasing your stats each by %d up to +11.
+		return ([[You concentrate on your inner self, increasing your stats each by %d.
 		The stat increase will improve with your Spellpower.]]):
 		format(statinc)
 	end,
diff --git a/game/modules/tome/data/talents/spells/explosives.lua b/game/modules/tome/data/talents/spells/explosives.lua
index 233b606a14cac068a64a857fe034c086f30d1851..a38d594ac0812a575e4f623c7e4eece16ee36e9b 100644
--- a/game/modules/tome/data/talents/spells/explosives.lua
+++ b/game/modules/tome/data/talents/spells/explosives.lua
@@ -24,12 +24,8 @@ newTalent{
 	points = 5,
 	mana = 5,
 	cooldown = 4,
-	range = function(self, t)
-		return math.ceil(4 + self:getTalentLevelRaw(t))
-	end,
-	radius = function(self, t)
-		return util.bound(1+self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT), 1, 6)
-	end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9, 0.5, 0, 0, true)) end,
+	radius = function(self, t) return self:callTalent(self.T_EXPLOSION_EXPERT, "getRadius") end,
 	direct_hit = true,
 	requires_target = true,
 	target = function(self, t)
@@ -93,15 +89,11 @@ newTalent{
 
 		-- Compare theorical AOE zone with actual zone and adjust damage accordingly
 		if self:knowTalent(self.T_EXPLOSION_EXPERT) then
-			local theorical_nb = ({ 9, 25, 45, 77, 109, 145 })[tg.radius] or 145
 			local nb = 0
 			local grids = self:project(tg, x, y, function(tx, ty) end)
 			if grids then for px, ys in pairs(grids or {}) do for py, _ in pairs(ys) do nb = nb + 1 end end end
-			nb = theorical_nb - nb
 			if nb > 0 then
-				local mult = math.log10(nb) / (6 - math.min(self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT), 5))
-				print("Adjusting explosion damage to account for ", nb, " lost tiles => ", mult * 100)
-				dam = dam + dam * mult
+				dam = dam + dam * self:callTalent(self.T_EXPLOSION_EXPERT, "minmax", nb)
 			end
 		end
 
@@ -181,15 +173,27 @@ newTalent{
 	require = spells_req3,
 	mode = "passive",
 	points = 5,
+	getRadius = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6, 0.5, 0, 0, true)) end,
+	minmax = function(self, t, grids)
+		local theoretical_nb = (2 * t.getRadius(self, t) + 1)^1.94 -- Maximum grids hit vs. talent level
+		if grids then
+			local lostgrids = math.max(theoretical_nb - grids, 0)
+			local mult = math.max(0,math.log10(lostgrids)) / (6 - math.min(self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT), 5))
+			print("Adjusting explosion damage to account for ", lostgrids, " lost tiles => ", mult * 100)
+			return mult
+		else
+			local min = 1
+			local min = (math.log10(min) / (6 - math.min(self:getTalentLevelRaw(t), 5)))
+			local max = theoretical_nb
+			local max = (math.log10(max) / (6 - math.min(self:getTalentLevelRaw(t), 5)))
+			return min, max
+		end
+	end,
 	info = function(self, t)
-		local theorical_nb = ({ 9, 25, 45, 77, 109, 145 })[1 + self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT)] or 145
-		local min = 1
-		local min = (math.log10(min) / (6 - self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT)))
-		local max = theorical_nb
-		local max = (math.log10(max) / (6 - self:getTalentLevelRaw(self.T_EXPLOSION_EXPERT)))
-
+		local min, max = t.minmax(self, t)
 		return ([[Your alchemist bombs now affect a radius of %d around them.
-		Increases explosion damage by %d%% (one tile less than the full effect) to %d%% (explosion concentrated on only 1 tile)]]):format(self:getTalentLevelRaw(t), min*100, max*100)
+		Explosion damage may increase by %d%% (if the explosion is not contained) to %d%% if the area of effect is confined.]]):
+		format(t.getRadius(self, t), min*100, max*100) --I5
 	end,
 }
 
@@ -200,9 +204,7 @@ newTalent{
 	points = 5,
 	mana = 32,
 	cooldown = 10,
-	range = function(self, t)
-		return math.ceil(4 + self:getTalentLevelRaw(t))
-	end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9, 0.5, 0, 0, true)) end,
 	radius = 2,
 	direct_hit = true,
 	requires_target = true,
diff --git a/game/modules/tome/data/talents/spells/fire-alchemy.lua b/game/modules/tome/data/talents/spells/fire-alchemy.lua
index c32f18d450b72a002d71e2d6c0682b252e28aa45..f6bd5f3c5da80f15ae971711c941057b6dc0f1ae 100644
--- a/game/modules/tome/data/talents/spells/fire-alchemy.lua
+++ b/game/modules/tome/data/talents/spells/fire-alchemy.lua
@@ -32,7 +32,7 @@ newTalent{
 	direct_hit = true,
 	tactical = { ATTACK = { FIRE = 2 } },
 	requires_target = true,
-	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 25, 620) end,
+	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 25, 930) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
@@ -59,7 +59,7 @@ newTalent{
 	direct_hit = true,
 	tactical = { DISABLE = 2 },
 	requires_target = true,
-	getDuration = function(self, t) return 2 + self:combatSpellpower(0.03) * self:getTalentLevel(t) end,
+	getDuration = function(self, t) return math.floor(self:combatScale(self:combatSpellpower(0.03) * self:getTalentLevel(t), 2, 0, 10, 8)) end,
 	action = function(self, t)
 		local tg = {type="ball", range=self:getTalentRange(t), radius=2, talent=t}
 		local x, y = self:getTarget(tg)
@@ -127,7 +127,7 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, friendlyfire=false}
 	end,
 	tactical = { ATTACKAREA = { FIRE = 2 } },
-	getDuration = function(self, t) return 5 + self:combatSpellpower(0.05) + self:getTalentLevel(t) end,
+	getDuration = function(self, t) return math.floor(self:combatScale(self:combatSpellpower(0.05) + self:getTalentLevel(t), 5, 0, 12.67, 7.66)) end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 120) end,
 	action = function(self, t)
 		-- Add a lasting map effect
diff --git a/game/modules/tome/data/talents/spells/fire.lua b/game/modules/tome/data/talents/spells/fire.lua
index bc5d6cd7e36e68f222e92a3e9b20c207fa39e83a..b23fada6bcd61def05d32cccc54ae68f2c50d537 100644
--- a/game/modules/tome/data/talents/spells/fire.lua
+++ b/game/modules/tome/data/talents/spells/fire.lua
@@ -88,15 +88,13 @@ newTalent{
 		end
 	end },
 	range = 0,
-	radius = function(self, t)
-		return 3 + self:getTalentLevelRaw(t)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8, 0.5, 0, 0, true)) end,
 	requires_target = true,
 	target = function(self, t)
 		return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t}
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 10, 250) end,
-	getStunDuration = function(self, t) return self:getTalentLevelRaw(t) + 2 end,
+	getStunDuration = function(self, t) return self:combatTalentScale(t, 3, 7, 0.5, 0, 0, true) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -137,9 +135,7 @@ newTalent{
 	cooldown = 8,
 	tactical = { ATTACKAREA = { FIRE = 2 } },
 	range = 7,
-	radius = function(self, t)
-		return 1 + self:getTalentLevelRaw(t)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6, 0.5, 0, 0, true)) end,
 	proj_speed = 4,
 	direct_hit = true,
 	requires_target = true,
@@ -193,7 +189,7 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 80) end,
-	getDuration = function(self, t) return 5 + self:getTalentLevel(t) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
diff --git a/game/modules/tome/data/talents/spells/golem.lua b/game/modules/tome/data/talents/spells/golem.lua
index 74761e84f7985f9e812428e314c056edfb2e3b33..dd3934b5d9a34b556c1fe94cea1763db24813e64 100644
--- a/game/modules/tome/data/talents/spells/golem.lua
+++ b/game/modules/tome/data/talents/spells/golem.lua
@@ -78,7 +78,7 @@ newTalent{
 
 		-- Try to knockback !
 		if hit then
-			if target:checkHit(self:combatPhysicalpower(), target:combatPhysicalResist(), 0, 95, 5 - self:getTalentLevel(t) / 2) and target:canBe("knockback") then
+			if target:checkHit(self:combatPhysicalpower(), target:combatPhysicalResist(), 0, 95) and target:canBe("knockback") then -- Deprecated call to checkhitold
 				target:knockback(self.x, self.y, 3)
 				target:crossTierEffect(target.EFF_OFFBALANCE, self:combatPhysicalpower())
 			else
@@ -100,13 +100,9 @@ newTalent{
 	type = {"golem/fighting", 2},
 	require = techs_req2,
 	points = 5,
-	cooldown = function(self, t)
-		return 20 - self:getTalentLevelRaw(t) * 2
-	end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 18, 10, true)) end, -- Limit to > 0
 	range = 10,
-	radius = function(self, t)
-		return self:getTalentLevelRaw(t) / 2
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 0.5, 2.5)) end,
 	stamina = 5,
 	requires_target = true,
 	target = function(self, t)
@@ -134,7 +130,7 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Orders your golem to taunt targets in a radius of %d, forcing them to attack the golem.]]):format(self:getTalentLevelRaw(t) / 2 + 1)
+		return ([[The golem taunts targets in a radius of %d, forcing them to attack it.]]):format(self:getTalentRadius(t)) 
 	end,
 }
 
@@ -148,7 +144,7 @@ newTalent{
 	stamina = 5,
 	requires_target = true,
 	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.8, 1.6) end,
-	getPinDuration = function(self, t) return 2 + self:getTalentLevel(t) end,
+	getPinDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	tactical = { ATTACK = { PHYSICAL = 0.5 }, DISABLE = { pin = 2 } },
 	action = function(self, t)
 		if self:attr("never_move") then game.logPlayer(self, "Your golem cannot do that currently.") return end
@@ -227,7 +223,7 @@ newTalent{
 	getGolemDamage = function(self, t)
 		return self:combatTalentWeaponDamage(t, 0.4, 1.1)
 	end,
-	getDazeDuration = function(self, t) return 2 + self:getTalentLevel(t) end,
+	getDazeDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	tactical = { ATTACKAREA = { PHYSICAL = 0.5 }, DISABLE = { daze = 3 } },
 	action = function(self, t)
 		if self:attr("never_move") then game.logPlayer(self, "Your golem cannot do that currently.") return end
@@ -373,10 +369,11 @@ newTalent{
 	sustain_mana = 30,
 	requires_target = true,
 	tactical = { DEFEND = 1, SURROUNDED = 3, BUFF = 1 },
+	getReflect = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 12, 40), 100, 20, 0, 46.5, 26.5) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/spell_generic2")
 		local ret = {
-			tmpid = self:addTemporaryValue("reflect_damage", 20 + self:combatTalentSpellDamage(t, 12, 40))
+			tmpid = self:addTemporaryValue("reflect_damage", (t.getReflect(self, t)))
 		}
 		return ret
 	end,
@@ -389,7 +386,7 @@ newTalent{
 		Any damage it takes is partly reflected (%d%%) to the attacker.
 		The golem still takes full damage.
 		Damage returned will increase with your golem's Spellpower.]]):
-		format(20 + self:combatTalentSpellDamage(t, 12, 40))
+		format(t.getReflect(self, t))
 	end,
 }
 
@@ -400,9 +397,7 @@ newTalent{
 	points = 5,
 	cooldown = 15,
 	range = 0,
-	radius = function(self, t)
-		return 3 + self:getTalentLevel(t) / 2
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 3.5, 5.5)) end,
 	mana = 20,
 	requires_target = true,
 	target = function(self, t)
diff --git a/game/modules/tome/data/talents/spells/golemancy.lua b/game/modules/tome/data/talents/spells/golemancy.lua
index d7f6aaf10c41e183dc4617225ed55e07e4cd1b31..1771b775cff3caeebe5b638fcb54f54fcf51c2cf 100644
--- a/game/modules/tome/data/talents/spells/golemancy.lua
+++ b/game/modules/tome/data/talents/spells/golemancy.lua
@@ -34,7 +34,8 @@ local function makeGolem(self)
 		moddable_tile = "runic_golem",
 		moddable_tile_nude = true,
 		moddable_tile_base = resolvers.generic(function() return "base_0"..rng.range(1, 5)..".png" end),
-		level_range = {1, 50}, exp_worth=0,
+--		level_range = {1, 50}, exp_worth=0,
+		level_range = {1, self.max_level}, exp_worth=0,
 		life_rating = 13,
 		never_anger = true,
 		save_hotkeys = true,
@@ -152,8 +153,12 @@ newTalent{
 	no_npc_use = true,
 	no_unlearn_last = true,
 	getHeal = function(self, t)
+		if not self.alchemy_golem then return 0 end
 		local ammo = self:hasAlchemistWeapon()
-		return 50 + self:combatTalentSpellDamage(self.T_GOLEM_POWER, 15, 550, ((ammo and ammo.alchemist_power or 0) + self:combatSpellpower()) / 2)
+
+		--	Heal fraction of max life for higher levels
+		local healbase = 44+self.alchemy_golem.max_life*self:combatTalentLimit(self:getTalentLevel(self.T_GOLEM_POWER),0.2, 0.008, 0.033) -- Add up to 20% of max life to heal
+		return healbase + self:combatTalentSpellDamage(self.T_GOLEM_POWER, 15, 550, ((ammo and ammo.alchemist_power or 0) + self:combatSpellpower()) / 2) --I5
 	end,
 	on_learn = function(self, t)
 		if self:getTalentLevelRaw(t) == 1 and not self.innate_alchemy_golem then
@@ -386,7 +391,7 @@ newTalent{
 	require = spells_req4,
 	points = 5,
 	mana = 40,
-	cooldown = function(self, t) return 15 - self:getTalentLevelRaw(t) end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 14, 10, true)) end, -- Limit to > 0
 	action = function(self, t)
 		local mover, golem = getGolem(self)
 		if not golem then
@@ -394,7 +399,7 @@ newTalent{
 			return
 		end
 
-		local chance = self:getTalentLevelRaw(t) * 15 + 25
+		local chance = math.min(100, self:getTalentLevelRaw(t) * 15 + 25)
 		local px, py = self.x, self.y
 		local gx, gy = golem.x, golem.y
 
@@ -419,6 +424,6 @@ newTalent{
 	end,
 	info = function(self, t)
 		return ([[Teleport to your golem, while your golem teleports to your location. Your foes will be confused, and those that were attacking you will have a %d%% chance to target your golem instead.]]):
-		format(self:getTalentLevelRaw(t) * 15 + 25)
+		format(math.min(100, self:getTalentLevelRaw(t) * 15 + 25))
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/grave.lua b/game/modules/tome/data/talents/spells/grave.lua
index e5780edd7a0f18c2b5f8baedc304b790f4ba5587..989d9ac8f0a3ed26a5a0e24f4a91166d50ae5c0f 100644
--- a/game/modules/tome/data/talents/spells/grave.lua
+++ b/game/modules/tome/data/talents/spells/grave.lua
@@ -29,7 +29,7 @@ newTalent{
 	tactical = { ATTACKAREA = { COLD = 2 } },
 	range = 7,
 	radius = function(self, t)
-		return 1 + self:getTalentLevelRaw(t)
+		return math.floor(self:combatTalentScale(t, 2, 6, 0.5, 0, 0, true))
 	end,
 	proj_speed = 4,
 	direct_hit = true,
@@ -262,9 +262,7 @@ newTalent{
 		end
 	end,
 
-	getDarkCount = function(self, t)
-		return 5 + math.floor(self:getTalentLevel(t))
-	end,
+	getDarkCount = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	getDamage = function(self, t)
 		return self:combatTalentSpellDamage(t, 10, 90)
 	end,
@@ -330,7 +328,7 @@ newTalent{
 	sustain_mana = 250,
 	cooldown = 30,
 	tactical = { BUFF = 3 },
-	getParams = function(self, t) return 20 + self:getTalentLevel(t) * 5, 5 + self:combatTalentSpellDamage(t, 5, 30) end,
+	getParams = function(self, t) return self:combatTalentLimit(t, 100, 25, 45), self:combatLimit(self:combatTalentSpellDamage(t, 5, 30), 100, 0, 0, 18.65, 18.65) end, -- Limit chance and life leach to <100% each
 	activate = function(self, t)
 		local chance, val = t.getParams(self, t)
 		game:playSoundNear(self, "talents/spell_generic2")
diff --git a/game/modules/tome/data/talents/spells/ice.lua b/game/modules/tome/data/talents/spells/ice.lua
index 493d88f51a1c4a46f88d6580dcb1699c071de821..be62ab0c2f5ac480c54acc9d3b0cb39dfa6a22fb 100644
--- a/game/modules/tome/data/talents/spells/ice.lua
+++ b/game/modules/tome/data/talents/spells/ice.lua
@@ -67,7 +67,7 @@ newTalent{
 	requires_target = true,
 	tactical = { ATTACKAREA = { COLD = 2 }, DISABLE = { stun = 1 } },
 	range = 0,
-	radius = function(self, t) return 1 + self:getTalentLevelRaw(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t}
 	end,
@@ -154,8 +154,8 @@ newTalent{
 	cooldown = 30,
 	tactical = { BUFF = 2 },
 	getColdDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
-	getPierce = function(self, t) return self:getTalentLevelRaw(t) * 20 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50) end, -- Limit < 100
+	getPierce = function(self, t) return math.max(100, self:getTalentLevelRaw(t) * 20) end, 
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/ice")
 
diff --git a/game/modules/tome/data/talents/spells/infusion.lua b/game/modules/tome/data/talents/spells/infusion.lua
index d74d5363dbf0777bddcad57225792a1a9b9dde98..6f05aee165ef5de3f63b8b8c0fa6295609273f7d 100644
--- a/game/modules/tome/data/talents/spells/infusion.lua
+++ b/game/modules/tome/data/talents/spells/infusion.lua
@@ -32,7 +32,7 @@ newTalent{
 	mode = "passive",
 	require = spells_req1,
 	points = 5,
-	getIncrease = function(self, t) return self:getTalentLevel(t) * 0.07 end,
+	getIncrease = function(self, t) return self:combatTalentScale(t, 0.07, 0.35) end,
 	info = function(self, t)
 		local daminc = t.getIncrease(self, t)
 		return ([[When you throw your alchemist bombs, you infuse them with explosive fire, increasing damage by %d%%, and setting foes ablaze.]]):
@@ -49,8 +49,8 @@ newTalent{
 	points = 5,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	getIncrease = function(self, t) return self:getTalentLevel(t) * 0.05 end,
-	getConvert = function(self, t) return self:getTalentLevelRaw(t) * 15 end,
+	getIncrease = function(self, t) return self:combatTalentScale(t, 0.05, 0.25) end,
+	getConvert = function(self, t) return self:combatTalentLimit(t, 125, 15, 75, true) end, -- limit to 125%
 	activate = function(self, t)
 		cancelInfusions(self)
 		game:playSoundNear(self, "talents/arcane")
@@ -80,8 +80,8 @@ newTalent{
 	points = 5,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	getIncrease = function(self, t) return self:getTalentLevel(t) * 0.05 end,
-	getConvert = function(self, t) return self:getTalentLevelRaw(t) * 15 end,
+	getIncrease = function(self, t) return self:combatTalentScale(t, 0.05, 0.25) end,
+	getConvert = function(self, t) return self:combatTalentLimit(t, 125, 15, 75, true) end, -- limit to 125%
 	activate = function(self, t)
 		cancelInfusions(self)
 		game:playSoundNear(self, "talents/arcane")
@@ -111,8 +111,8 @@ newTalent{
 	points = 5,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	getIncrease = function(self, t) return self:getTalentLevel(t) * 0.05 end,
-	getConvert = function(self, t) return self:getTalentLevelRaw(t) * 15 end,
+	getIncrease = function(self, t) return self:combatTalentScale(t, 0.05, 0.25) end,
+	getConvert = function(self, t) return self:combatTalentLimit(t, 125, 15, 75, true) end, -- limit to 125%
 	activate = function(self, t)
 		cancelInfusions(self)
 		game:playSoundNear(self, "talents/arcane")
diff --git a/game/modules/tome/data/talents/spells/meta.lua b/game/modules/tome/data/talents/spells/meta.lua
index c2edb54eb171b42823f7e5935d2d28b12844c902..538cdd46867f6c806989f111fde95049f5955a4c 100644
--- a/game/modules/tome/data/talents/spells/meta.lua
+++ b/game/modules/tome/data/talents/spells/meta.lua
@@ -29,7 +29,7 @@ newTalent{
 	direct_hit = true,
 	requires_target = function(self, t) return self:getTalentLevel(t) >= 3 end,
 	range = 10,
-	getRemoveCount = function(self, t) return math.floor(self:getTalentLevel(t)) end,
+	getRemoveCount = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
 	action = function(self, t)
 		local target = self
 
@@ -159,13 +159,13 @@ newTalent{
 	mana = 70,
 	cooldown = 50,
 	tactical = { BUFF = 2 },
-	getTalentCount = function(self, t) return math.ceil(self:getTalentLevel(t) + 2) end,
-	getMaxLevel = function(self, t) return self:getTalentLevelRaw(t) end,
+	getTalentCount = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
+	getMaxLevel = function(self, t) return self:getTalentLevel(t) end,
 	action = function(self, t)
 		local tids = {}
 		for tid, _ in pairs(self.talents_cd) do
 			local tt = self:getTalentFromId(tid)
-			if tt.type[2] <= t.getMaxLevel(self, t) and tt.is_spell then
+			if self:getTalentLevel(tt) <= t.getMaxLevel(self, t) and tt.is_spell then
 				tids[#tids+1] = tid
 			end
 		end
@@ -181,7 +181,7 @@ newTalent{
 	info = function(self, t)
 		local talentcount = t.getTalentCount(self, t)
 		local maxlevel = t.getMaxLevel(self, t)
-		return ([[Your mastery of the arcane flows allow you to reset the cooldown of %d of your spells of tier %d or less.]]):
+		return ([[Your mastery of the arcane flows allow you to reset the cooldown of %d of your spells of talent level %0.1f or less.]]):
 		format(talentcount, maxlevel)
 	end,
 }
diff --git a/game/modules/tome/data/talents/spells/necrosis.lua b/game/modules/tome/data/talents/spells/necrosis.lua
index 44bb9be1a43fedc605e3e42bb9b0eaf2974af455..c71fd90311076e32c729bd9d6d723f0e891894ad 100644
--- a/game/modules/tome/data/talents/spells/necrosis.lua
+++ b/game/modules/tome/data/talents/spells/necrosis.lua
@@ -26,6 +26,9 @@ newTalent{
 	sustain_mana = 30,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
+	lifeBonus = function(self, t) -- Add fraction of max life
+		return 50 * self:getTalentLevelRaw(t) + self.max_life * self:combatTalentLimit(t, 1, .01, .05)
+	end,
 	activate = function(self, t)
 		if self.player and not self:hasQuest("lichform") and not self:attr("undead") then
 			self:grantQuest("lichform")
@@ -34,8 +37,8 @@ newTalent{
 		end
 
 		local ret = {
-			die_at = self:addTemporaryValue("die_at", -50 * self:getTalentLevelRaw(t)),
-		}
+			die_at = self:addTemporaryValue("die_at", -t.lifeBonus(self, t)),
+		} -- Add up to 100% max life
 		return ret
 	end,
 	deactivate = function(self, t, p)
@@ -45,7 +48,7 @@ newTalent{
 	info = function(self, t)
 		return ([[The line between life and death blurs for you; you can only die when you reach -%d life.
 		However, when below 0 HP, you cannot see how much life you have left.]]):
-		format(50 * self:getTalentLevelRaw(t))
+		format(t.lifeBonus(self, t))
 	end,
 }
 
@@ -60,7 +63,7 @@ newTalent{
 	range = 7,
 	requires_target = true,
 	getMax = function(self, t) return 200 + self:combatTalentSpellDamage(t, 28, 850) end,
-	getDamage = function(self, t) return 50 + self:combatTalentSpellDamage(t, 10, 100) end,
+	getDamage = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 100), 150, 50, 0, 117, 67) end, -- Limit damage factor to < 150%
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
@@ -91,7 +94,7 @@ newTalent{
 	cooldown = 20,
 	tactical = { HEAL = 2 },
 	is_heal = true,
-	getHeal = function(self, t) return 20 + self:combatTalentSpellDamage(t, 10, 70) end,
+	getHeal = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 70), 100, 20, 0,  66.7, 46.7) end, --Limit to <100%
 	on_pre_use = function(self, t)
 		if game.party and game.party:hasMember(self) then
 			for act, def in pairs(game.party.members) do
@@ -110,30 +113,36 @@ newTalent{
 	end,
 	action = function(self, t)
 		local heal = t.getHeal(self, t)
+		local maxdrain = 0 --Use biggest drain for healing purposes
+		local drain = 0
 		if game.party and game.party:hasMember(self) then
 			for act, def in pairs(game.party.members) do
 				if act.summoner and act.summoner == self and act.necrotic_minion then
-					act:takeHit(act.max_life * heal / 100, self)
+					drain = math.min(act.max_life * heal / 100, act.life-act.die_at)
+					act:takeHit(drain, self)
+					maxdrain = math.max(maxdrain, drain)
 				end
 			end
 		else
 			for uid, act in pairs(game.level.entities) do
 				if act.summoner and act.summoner == self and act.necrotic_minion then
-					act:takeHit(act.max_life * heal / 100, self)
+					drain = math.min(act.max_life * heal / 100, act.life-act.die_at)
+					act:takeHit(drain, self)
+					maxdrain = math.max(maxdrain, drain)
 				end
 			end
 		end
 		self:attr("allow_on_heal", 1)
-		self:heal(self.max_life * heal / 100)
+		self:heal(maxdrain)
 		self:attr("allow_on_heal", -1)
 		game:playSoundNear(self, "talents/ice")
 		return true
 	end,
 	info = function(self, t)
 		local heal = t.getHeal(self, t)
-		return ([[Absorb %d%% of all your minions' life (possibly destroying them) and use this energy to heal you for %d%% of your total life.
+		return ([[Absorb up to %d%% of the maximum life of each of your necrotic minions (even negative life, possibly destroying them). This will heal you for the greatest amount absorbed.
 		The healing will increase with your Spellpower.]]):
-		format(heal, heal)
+		format(heal)
 	end,
 }
 
diff --git a/game/modules/tome/data/talents/spells/necrotic-minions.lua b/game/modules/tome/data/talents/spells/necrotic-minions.lua
index 3edd5e56c8c8b83d7673c941d905a727ab1f6aaa..c127291fc2372e2a7e66d65867f5465e76b94a0b 100644
--- a/game/modules/tome/data/talents/spells/necrotic-minions.lua
+++ b/game/modules/tome/data/talents/spells/necrotic-minions.lua
@@ -57,7 +57,7 @@ newTalent{
 		}, 40)
 	end,
 	getDecay = function(self, t) return math.max(3, 10 - self:getTalentLevelRaw(self.T_AURA_MASTERY)) end,
-	getRadius = function(self, t) return 2 + self:getTalentLevelRaw(self.T_AURA_MASTERY) end,
+	getRadius = function(self, t) return 2 + self:callTalent(self.T_AURA_MASTERY, "getbonusRadius") end,
 	activate = function(self, t)
 		local radius = t.getRadius(self, t)
 		local decay = t.getDecay(self, t)
@@ -600,7 +600,7 @@ local minions_list = {
 		},
 	},
 }
-
+--[[
 function getAdvancedMinionChances(self)
 	local cl = math.floor(self:getTalentLevel(self.T_MINION_MASTERY))
 	if cl <= 1 then
@@ -653,6 +653,75 @@ local function makeMinion(self, lev)
 	local m = require("mod.class.NPC").new(minions_list[rng.table(list)])
 	return m
 end
+--]]
+
+local minion_order = {"d_skel_warrior", "skel_warrior", "a_skel_warrior", "skel_archer", "skel_m_archer", "skel_mage", "ghoul", "ghast", "ghoulking","vampire", "m_vampire", "g_wight", "b_wight", "dread", "lich"} -- Sets listing order 
+
+-- Parameters are b, n, m, p where weight = b + n*tl + m*tl^p
+local MinionWeightParams = {
+	d_skel_warrior={92.944,	0.000,	-37.944, 0.500},
+	skel_warrior={	7.000,	0.000,	3.000,	1.000},
+	a_skel_warrior={-3.000,	0.000,	3.000,	1.000},
+	skel_archer={	0.000,	11.667,	-1.667,	2.000},
+	skel_m_archer={	-2.000,	0.000,	2.000,	1.000},
+	skel_mage={		1.471,	0.000,	3.529,	0.750},
+	ghoul={			23.000,	0.000,	-3.000,	1.000},
+	ghast={			-3.529,	0.000,	3.529,	0.750},
+	ghoulking={		-7.816,	0.000,	4.647,	0.750}
+}
+
+local AdvMinionWeightParams = {
+	vampire={	2.690,	3.139,	-0.448,	2.000},
+	m_vampire={	-1.076,	0.000,	1.076,	1.000},
+	g_wight={	-2.690,	0.000,	1.345,	1.000},
+	b_wight={	-5.381,	0.000,	1.794,	1.000},
+	dread={		-1.614,	0.000,	1.614,	1.000},
+	lich={		-5.381,	0.000,	1.794,	1.000}
+}
+
+-- tl = talent level, or {createminionsTL, minionmasteryTL}
+-- wtable = weight table or table of weight tables
+local function getMinionWeights(tl,wtable)
+	local tl = tl
+	if type(tl) == "number" then tl = {tl} end
+	local tables = #wtable > 0 and #wtable or 1
+	local chances, sum = {}, 0
+	
+	for i = 1, tables do
+		for utype, params in pairs(tables > 1 and wtable[i] or wtable) do
+			chances[utype] = math.max(0,params[1] + params[2]*tl[i] + params[3]*tl[i]^params[4])
+			sum = sum + chances[utype]
+		end
+	end
+	return chances, sum
+end
+
+local function getMinionChances(self)
+	local chances, sum
+	local tlcm, tlmm = self:getTalentLevel(self.T_CREATE_MINIONS),self:getTalentLevel(self.T_MINION_MASTERY)
+	if tlmm > 0 then
+		chances, sum = getMinionWeights({math.max(tlcm,tlmm), tlmm},{MinionWeightParams, AdvMinionWeightParams}) -- Balance talent levels to avoid too many powerful minions
+	else
+		chances, sum = getMinionWeights(tlcm,MinionWeightParams)
+	end
+	for i,k in pairs(chances) do
+		chances[i] = k*100/sum
+	end
+	return chances
+end
+
+local function makeMinion(self, lev)
+	local chances = getMinionChances(self)
+	local pick = rng.float(0,100)
+	local tot, m = 0
+	for k, e in pairs(chances) do
+		tot = tot + e
+		if tot > pick then m = k break end
+	end
+	m = require("mod.class.NPC").new(minions_list[m])
+	m.necrotic_minion = true
+	return m
+end
 
 newTalent{
 	name = "Create Minions",
@@ -686,12 +755,15 @@ newTalent{
 		if math.min(nb, p.souls) < 1 then return end
 		return true
 	end,
-	getMax = function(self, t) return math.floor(self:getTalentLevel(t)) - necroGetNbSummon(self) end,
-	getLevel = function(self, t)
-		local raw = self:getTalentLevelRaw(t)
-		if raw <= 0 then return -8 end
-		if raw > 8 then return 8 end
-		return ({-6, -4, -2, 0, 2, 4, 6, 8})[raw]
+	getMax = function(self, t) return math.max(1, math.floor(self:combatTalentScale(t, 1, 5, "log"))) - necroGetNbSummon(self) end, -- talent level 1-5 gives 1-5
+	getLevel = function(self, t) return math.floor(self:combatScale(self:getTalentLevel(t), -6, 0.9, 2, 5)) end, -- -6 @ 1, +2 @ 5, +5 @ 8
+	MinionChancesDesc = function(self)
+		local c = getMinionChances(self)
+		local chancelist = tstring({})
+		for i, k in ipairs(minion_order) do
+			 if c[k] then chancelist:add(true,minions_list[k].name:capitalize(),(": %d%%"):format(c[k])) end
+		end
+		return chancelist:toString()
 	end,
 	action = function(self, t)
 		local p = self:isTalentActive(self.T_NECROTIC_AURA)
@@ -732,21 +804,11 @@ newTalent{
 	info = function(self, t)
 		local nb = t.getMax(self, t)
 		local lev = t.getLevel(self, t)
-		local c = getMinionChances(self)
+		local mm = self:knowTalent(self.T_MINION_MASTERY) and " (Minion Mastery effects included)" or ""
 		return ([[Fires powerful undead energies through your necrotic aura. For each recent death that happened inside your aura, you will raise an undead minion (up to %d minions). These minions will be raised within a cone that extends to the edge of your necrotic aura.
 		The minions level is your level %+d.
-		Each minion has a chance to be:
-		Degenerated skeleton warrior: %d%%
-		Skeleton warrior: %d%%
-		Armoured skeleton warrior: %d%%
-		Skeleton archer: %d%%
-		Skeleton master archer: %d%%
-		Skeleton mage: %d%%
-		Ghoul: %d%%
-		Ghast: %d%%
-		Ghoulking: %d%%
-		]]):
-		format(nb, lev, c.d_skel_warrior, c.skel_warrior, c.a_skel_warrior, c.skel_archer, c.skel_m_archer, c.skel_mage, c.ghoul, c.ghast, c.ghoulking)
+		Each minion has a chance to be%s:%s]]):
+		format(nb, lev, mm, t.MinionChancesDesc(self, t))
 	end,
 }
 
@@ -756,6 +818,7 @@ newTalent{
 	require = spells_req2,
 	points = 5,
 	mode = "passive",
+	getbonusRadius = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
 	on_learn = function(self, t)
 		self:forceUseTalent(self.T_NECROTIC_AURA, {ignore_energy=true, ignore_cd=true, no_equilibrium_fail=true, no_paradox_fail=true})
 		self:forceUseTalent(self.T_NECROTIC_AURA, {ignore_energy=true, ignore_cd=true, no_equilibrium_fail=true, no_paradox_fail=true})
@@ -766,7 +829,7 @@ newTalent{
 	end,
 	info = function(self, t)
 		return ([[Your dark power radiates further as you grow stronger. Increases the radius of your necrotic aura by %d, and reduces the decay rate of your minions outside the aura by %d%%.]]):
-		format(self:getTalentLevelRaw(t), self:getTalentLevelRaw(t))
+		format(math.floor(t.getbonusRadius(self, t)), math.min(7, self:getTalentLevelRaw(t)))
 	end,
 }
 
diff --git a/game/modules/tome/data/talents/spells/nightfall.lua b/game/modules/tome/data/talents/spells/nightfall.lua
index 1d9736e483752ea0ad50bb823d60ee5f61738761..c81a53987f1fca490ee6b9dbd775481b652892e9 100644
--- a/game/modules/tome/data/talents/spells/nightfall.lua
+++ b/game/modules/tome/data/talents/spells/nightfall.lua
@@ -87,6 +87,7 @@ newTalent{
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 40) end,
 	getDuration = function(self, t) return 5 end,
+	getBaneDur = function(self,t) return math.floor(self:combatTalentScale(t, 4.5, 6.5)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -95,7 +96,7 @@ newTalent{
 		-- Add a lasting map effect
 		game.level.map:addEffect(self,
 			x, y, t.getDuration(self, t),
-			DamageType.CIRCLE_DEATH, {dam=self:spellCrit(t.getDamage(self, t)), dur=4 + math.floor(self:getTalentLevel(t) / 2), ff=isFF(self)},
+			DamageType.CIRCLE_DEATH, {dam=self:spellCrit(t.getDamage(self, t)), dur=t.getBaneDur(self,t), ff=isFF(self)},
 			self:getTalentRadius(t),
 			5, nil,
 			{type="circle_of_death"},
@@ -111,7 +112,7 @@ newTalent{
 		Only one bane can affect a creature.
 		Banes last for %d turns, and also deal %0.2f darkness damage.
 		The damage will increase with your Spellpower.]]):
-		format(4 + math.floor(self:getTalentLevel(t) / 2), damDesc(self, DamageType.DARKNESS, damage))
+		format(t.getBaneDur(self,t), damDesc(self, DamageType.DARKNESS, damage))
 	end,
 }
 
@@ -126,7 +127,7 @@ newTalent{
 	direct_hit = true,
 	tactical = { ATTACKAREA = { DARKNESS = 2 }, DISABLE = { knockback = 2 }, ESCAPE = { knockback = 1 } },
 	range = 0,
-	radius = function(self, t) return 3 + self:getTalentLevelRaw(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	requires_target = true,
 	target = function(self, t) return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), friendlyfire=isFF(self), talent=t} end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 10, 230) end,
@@ -163,7 +164,7 @@ newTalent{
 	target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), friendlyfire=isFF(self), talent=t, display={particle="bolt_dark", trail="darktrail"}} end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 28, 280) end,
 	getMinion = function(self, t) return 10 + self:combatTalentSpellDamage(t, 10, 30) end,
-	getDur = function(self, t) return math.floor(3 + self:getTalentLevel(t) / 1.5) end,
+	getDur = function(self, t) return math.floor(self:combatTalentScale(t, 3.6, 6.3)) end,
 	getSpeed = function(self, t) return math.min(self:getTalentLevel(t) * 0.065, 0.5) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
diff --git a/game/modules/tome/data/talents/spells/phantasm.lua b/game/modules/tome/data/talents/spells/phantasm.lua
index 07f85c5f7b28a2522255e21f083bb14e26084f51..082a66908f0759cf46af37ba1ad8247bd69099ae 100644
--- a/game/modules/tome/data/talents/spells/phantasm.lua
+++ b/game/modules/tome/data/talents/spells/phantasm.lua
@@ -26,7 +26,7 @@ newTalent{
 	mana = 5,
 	cooldown = 14,
 	range = 0,
-	radius = function(self, t) return 5 + self:getTalentLevel(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	tactical = { DISABLE = function(self, t)
 			if self:getTalentLevel(t) >= 3 then
 				return 2
@@ -77,7 +77,7 @@ newTalent{
 	sustain_mana = 30,
 	cooldown = 10,
 	tactical = { BUFF = 2 },
-	getDefense = function(self, t) return self:combatTalentSpellDamage(t, 6, 45) end,
+	getDefense = function(self, t) return self:combatScale(self:getTalentLevel(t)*self:combatSpellpower(), 0, 0, 28.6, 267, 0.75) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/heal")
 		return {
diff --git a/game/modules/tome/data/talents/spells/shades.lua b/game/modules/tome/data/talents/spells/shades.lua
index 6343f6ade76f1f8f982ccc079df4ba6381fc69e7..2f5d23fe0a432ba2dcf295a66e814c0fd37f1b3b 100644
--- a/game/modules/tome/data/talents/spells/shades.lua
+++ b/game/modules/tome/data/talents/spells/shades.lua
@@ -168,9 +168,9 @@ newTalent{
 	range = 10,
 	tactical = { ATTACK = 2, },
 	requires_target = true,
-	getDuration = function(self, t) return math.floor(3 + self:getTalentLevel(t)) end,
-	getHealth = function(self, t) return 0.2 + self:combatTalentSpellDamage(t, 20, 500) / 1000 end,
-	getDam = function(self, t) return 0.4 + self:combatTalentSpellDamage(t, 10, 500) / 1000 end,
+	getDuration = function(self, t) return math.floor(self:combatTalentLimit(t, 30, 4, 8.1)) end, -- Limit <30
+	getHealth = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 20, 500), 1.0, 0.2, 0, 0.58, 384) end,  -- Limit health < 100%
+	getDam = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 500), 1.40, 0.4, 0, 0.76, 361) end,  -- Limit damage < 140%
 	action = function(self, t)
 		-- Find space
 		local x, y = util.findFreeGrid(self.x, self.y, 1, true, {[Map.ACTOR]=true})
@@ -246,8 +246,8 @@ newTalent{
 	cooldown = 30,
 	tactical = { BUFF = 2 },
 	getDarknessDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
-	getAffinity = function(self, t) return self:getTalentLevel(t) * 10 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50, true) end,  -- Limit to < 100%
+	getAffinity = function(self, t) return self:combatTalentLimit(t, 100, 10, 50) end, -- Limit < 100%
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/spell_generic")
 		return {
diff --git a/game/modules/tome/data/talents/spells/staff-combat.lua b/game/modules/tome/data/talents/spells/staff-combat.lua
index a692554ff2e62e60cb1da0a28882a3a76d114515..1f149980d771d1ca9be887b5a837fef7dd52d57d 100644
--- a/game/modules/tome/data/talents/spells/staff-combat.lua
+++ b/game/modules/tome/data/talents/spells/staff-combat.lua
@@ -147,7 +147,7 @@ newTalent{
 		return {type="hit", range=self:getTalentRange(t)}
 	end,
 	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1, 1.5) end,
-	getDazeDuration = function(self, t) return 1 + self:getTalentLevel(t) end,
+	getDazeDuration = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	action = function(self, t)
 		local weapon = self:hasStaffWeapon()
 		if not weapon then
diff --git a/game/modules/tome/data/talents/spells/stone-alchemy.lua b/game/modules/tome/data/talents/spells/stone-alchemy.lua
index 2be0151323bbb5cd5a661a21d672b4d05d348b81..834577798c5ebc2d7a1bf8e1c3e156e3b52a72fa 100644
--- a/game/modules/tome/data/talents/spells/stone-alchemy.lua
+++ b/game/modules/tome/data/talents/spells/stone-alchemy.lua
@@ -162,7 +162,7 @@ newTalent{
 	points = 5,
 	range = 1,
 	no_npc_use = true,
-	getRange = function(self, t) return math.floor(4 + self:combatSpellpower(0.06) * self:getTalentLevel(t)) end,
+	getRange = function(self, t) return math.floor(self:combatScale(self:combatSpellpower(0.06) * self:getTalentLevel(t), 4, 0, 20, 16)) end,
 	action = function(self, t)
 		local ammo = self:hasAlchemistWeapon()
 		if not ammo or ammo:getNumber() < 5 then
@@ -205,7 +205,7 @@ newTalent{
 	tactical = { DISABLE = { stun = 1.5, instakill = 1.5 } },
 	range = function(self, t)
 		if self:getTalentLevel(t) < 3 then return 1
-		else return math.floor(self:getTalentLevel(t)) end
+		else return math.floor(self:combatTalentScale(t, 1, 5)) end
 	end,
 	requires_target = true,
 	target = function(self, t)
@@ -213,7 +213,7 @@ newTalent{
 		if self:getTalentLevel(t) >= 3 then tg.type = "beam" end
 		return tg
 	end,
-	getDuration = function(self, t) return math.floor((3 + self:getTalentLevel(t)) / 1.5) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3.6, 6.3)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
diff --git a/game/modules/tome/data/talents/spells/stone.lua b/game/modules/tome/data/talents/spells/stone.lua
index 4f93c86f41e6f7565d5e9a7867be916afe1df5ef..b1c8ecba77a9ef1036e3c3b535639c46b66ca4c8 100644
--- a/game/modules/tome/data/talents/spells/stone.lua
+++ b/game/modules/tome/data/talents/spells/stone.lua
@@ -84,7 +84,7 @@ newTalent{
 	getLightningRes = function(self, t) return self:combatTalentSpellDamage(t, 5, 50) end,
 	getAcidRes = function(self, t) return self:combatTalentSpellDamage(t, 5, 20) end,
 	getStunRes = function(self, t) return self:getTalentLevel(t)/10 end,
-	getCooldownReduction = function(self, t) return self:getTalentLevel(t)/2 end,
+	getCooldownReduction = function(self, t) return self:combatTalentLimit(t, 50, 16.7, 35.7) end,  -- Limit to < 50%
 	activate = function(self, t)
 		local cdr = t.getCooldownReduction(self, t)
 		game:playSoundNear(self, "talents/earth")
@@ -93,11 +93,12 @@ newTalent{
 			move = self:addTemporaryValue("never_move", 1),
 			stun = self:addTemporaryValue("stun_immune", t.getStunRes(self, t)),
 			cdred = self:addTemporaryValue("talent_cd_reduction", {
-				[self.T_EARTHEN_MISSILES] = cdr,
-				[self.T_MUDSLIDE] = cdr,
-				[self.T_DIG] = cdr,
-				[self.T_EARTHQUAKE] = cdr,
+				[self.T_EARTHEN_MISSILES] = math.floor(cdr*self:getTalentCooldown(self.talents_def.T_EARTHEN_MISSILES)/100),
+				[self.T_MUDSLIDE] = math.floor(cdr*self:getTalentCooldown(self.talents_def.T_MUDSLIDE)/100),
+				[self.T_DIG] = math.floor(cdr*self:getTalentCooldown(self.talents_def.T_DIG)/100),
+				[self.T_EARTHQUAKE] = math.floor(cdr*self:getTalentCooldown(self.talents_def.T_EARTHQUAKE)/100),
 			}),
+			
 			res = self:addTemporaryValue("resists", {
 				[DamageType.FIRE] = t.getFireRes(self, t),
 				[DamageType.LIGHTNING] = t.getLightningRes(self, t),
@@ -121,7 +122,7 @@ newTalent{
 		local stunres = t.getStunRes(self, t)
 		return ([[You root yourself into the earth, and transform your flesh into stone.  While this spell is sustained, you may not move, and any forced movement will end the effect.
 		Your stone form and your affinity with the earth while the spell is active has the following effects:
-		* Reduces the cooldown of Earthen Missiles, Pulveziring Auger, Earthquake, and Mudslide by %d.
+		* Reduces the cooldown of Earthen Missiles, Pulveziring Auger, Earthquake, and Mudslide by %d%%.
 		* Grants %d%% Fire Resistance, %d%% Lightning Resistance, %d%% Acid Resistance, and %d%% Stun Resistance.
 		Resistances scale with your Spellpower.]])
 		:format(cooldownred, fireres, lightningres, acidres, stunres*100)
@@ -138,16 +139,14 @@ newTalent{
 	cooldown = 30,
 	tactical = { ATTACKAREA = { PHYSICAL = 2 }, DISABLE = { stun = 3 } },
 	range = 10,
-	radius = function(self, t)
-		return 2 + (self:getTalentLevel(t)/2)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2.5, 4.5)) end,
 	direct_hit = true,
 	requires_target = 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, 15, 80) end,
-	getDuration = function(self, t) return 3 + self:getTalentLevel(t) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -186,7 +185,7 @@ newTalent{
 	cooldown = 30,
 	tactical = { BUFF = 2 },
 	getPhysicalDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50, true) end,  -- Limit to < 100%
 	getSaves = function(self, t) return self:getTalentLevel(t) * 5 end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/earth")
diff --git a/game/modules/tome/data/talents/spells/storm.lua b/game/modules/tome/data/talents/spells/storm.lua
index 5ba34cedbff65be4b86dd72489d731c2b55fd10f..98ae783a787110370fb69f4005ae7661596c74c8 100644
--- a/game/modules/tome/data/talents/spells/storm.lua
+++ b/game/modules/tome/data/talents/spells/storm.lua
@@ -26,9 +26,7 @@ newTalent{
 	cooldown = 8,
 	tactical = { ATTACKAREA = { LIGHTNING = 2 }, DISABLE = { stun = 1 } },
 	range = 0,
-	radius = function(self, t)
-		return math.floor(2 + self:getTalentLevel(t) * 0.7)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2.7, 5.5)) end,
 	direct_hit = true,
 	requires_target = true,
 	target = function(self, t)
@@ -146,7 +144,7 @@ newTalent{
 	sustain_mana = 50,
 	cooldown = 30,
 	getLightningDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50) end,
 	getDaze = function(self, t) return self:getTalentLevel(t) * 9 end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/thunderstorm")
diff --git a/game/modules/tome/data/talents/spells/temporal.lua b/game/modules/tome/data/talents/spells/temporal.lua
index 38ec3baded97f15801b71f8c8eb0e1c71d6aa41b..52e484305e08d0bc3a111076b8ee1bbb972b3243 100644
--- a/game/modules/tome/data/talents/spells/temporal.lua
+++ b/game/modules/tome/data/talents/spells/temporal.lua
@@ -96,7 +96,7 @@ newTalent{
 	direct_hit = true,
 	reflectable = true,
 	requires_target = true,
-	getDuration = function(self, t) return 4 + self:combatSpellpower(0.03) * self:getTalentLevel(t) end,
+	getDuration = function(self, t) return math.floor(self:combatScale(self:combatSpellpower(0.03) * self:getTalentLevel(t), 4, 0, 12, 8)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
@@ -123,7 +123,7 @@ newTalent{
 	sustain_mana = 250,
 	cooldown = 30,
 	tactical = { BUFF = 2 },
-	getHaste = function(self, t) return self:getTalentLevel(t) * 0.09 end,
+	getHaste = function(self, t) return self:combatTalentScale(t, 0.09, 0.45, 0.75) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/spell_generic")
 		local power = t.getHaste(self, t)
diff --git a/game/modules/tome/data/talents/spells/water.lua b/game/modules/tome/data/talents/spells/water.lua
index 1ae3fc8ca6f986d3456dc8faae71cc9b1637f89c..1178c4c58eabd8f1e961183e30f2e5e0152437d0 100644
--- a/game/modules/tome/data/talents/spells/water.lua
+++ b/game/modules/tome/data/talents/spells/water.lua
@@ -34,7 +34,7 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 4, 50) end,
-	getDuration = function(self, t) return self:getTalentLevel(t) + 2 end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -68,27 +68,28 @@ newTalent{
 	points = 5,
 	random_ego = "attack",
 	mana = 14,
-	cooldown = function(self, t) return 7 + self:getTalentLevelRaw(t) end,
+	cooldown = function(self, t) return math.floor(self:combatTalentLimit(t, 20, 8, 12, true)) end, -- Limit cooldown <20
 	tactical = { ATTACK = { COLD = 1 }, DISABLE = { stun = 3 } },
 	range = 10,
 	direct_hit = true,
 	reflectable = true,
 	requires_target = true,
-	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 12, 180) * (5 + self:getTalentLevelRaw(t)) / 5 end,
+	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 12, 180) * t.cooldown(self,t) / 6 end, -- Gradually increase burst potential with c/d
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		local dam = self:spellCrit(t.getDamage(self, t))
 		self:project(tg, x, y, DamageType.COLD, dam, {type="freeze"})
-		self:project(tg, x, y, DamageType.FREEZE, {dur=2+math.ceil(self:getTalentLevelRaw(t)), hp=70 + dam * 1.5})
+		self:project(tg, x, y, DamageType.FREEZE, {dur=t.getDuration(self, t), hp=70 + dam * 1.5})
 		game:playSoundNear(self, "talents/water")
 		return true
 	end,
 	info = function(self, t)
 		local damage = t.getDamage(self, t)
 		return ([[Condenses ambient water on a target, freezing it for %d turns and damaging it for %0.2f.
-		The damage will increase with your Spellpower.]]):format(2+math.ceil(self:getTalentLevelRaw(t)), damDesc(self, DamageType.COLD, damage))
+		The damage will increase with your Spellpower.]]):format(t.getDuration(self, t), damDesc(self, DamageType.COLD, damage))
 	end,
 }
 
diff --git a/game/modules/tome/data/talents/spells/wildfire.lua b/game/modules/tome/data/talents/spells/wildfire.lua
index 547b93bafcb72f685f3f2b9da53f4ab5a4d15372..55e3d536dc3703faf81efa4f6f234c366ed9e0d0 100644
--- a/game/modules/tome/data/talents/spells/wildfire.lua
+++ b/game/modules/tome/data/talents/spells/wildfire.lua
@@ -33,7 +33,7 @@ newTalent{
 	direct_hit = true,
 	requires_target = true,
 	range = 0,
-	radius = function(self, t) return 1 + self:getTalentLevelRaw(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t}
 	end,
@@ -117,8 +117,8 @@ newTalent{
 	cooldown = 30,
 	tactical = { BUFF = 2 },
 	getFireDamageIncrease = function(self, t) return self:getTalentLevelRaw(t) * 2 end,
-	getResistPenalty = function(self, t) return self:getTalentLevelRaw(t) * 10 end,
-	getResistSelf = function(self, t) return self:getTalentLevel(t) * 14 end,
+	getResistPenalty = function(self, t) return self:combatTalentLimit(t, 100, 17, 50) end, --Limit < 100%
+	getResistSelf = function(self, t) return math.min(100, self:getTalentLevel(t) * 14) end,
 	activate = function(self, t)
 		game:playSoundNear(self, "talents/fire")